Files
bDS2/priv/static/assets/app.js

9675 lines
330 KiB
JavaScript

(() => {
// ../deps/phoenix/priv/static/phoenix.mjs
var closure = (value) => {
if (typeof value === "function") {
return value;
} else {
let closure22 = function() {
return value;
};
return closure22;
}
};
var globalSelf = typeof self !== "undefined" ? self : null;
var phxWindow = typeof window !== "undefined" ? window : null;
var global = globalSelf || phxWindow || globalThis;
var DEFAULT_VSN = "2.0.0";
var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
var DEFAULT_TIMEOUT = 1e4;
var WS_CLOSE_NORMAL = 1e3;
var CHANNEL_STATES = {
closed: "closed",
errored: "errored",
joined: "joined",
joining: "joining",
leaving: "leaving"
};
var CHANNEL_EVENTS = {
close: "phx_close",
error: "phx_error",
join: "phx_join",
reply: "phx_reply",
leave: "phx_leave"
};
var TRANSPORTS = {
longpoll: "longpoll",
websocket: "websocket"
};
var XHR_STATES = {
complete: 4
};
var AUTH_TOKEN_PREFIX = "base64url.bearer.phx.";
var Push = class {
constructor(channel, event, payload, timeout) {
this.channel = channel;
this.event = event;
this.payload = payload || function() {
return {};
};
this.receivedResp = null;
this.timeout = timeout;
this.timeoutTimer = null;
this.recHooks = [];
this.sent = false;
}
/**
*
* @param {number} timeout
*/
resend(timeout) {
this.timeout = timeout;
this.reset();
this.send();
}
/**
*
*/
send() {
if (this.hasReceived("timeout")) {
return;
}
this.startTimeout();
this.sent = true;
this.channel.socket.push({
topic: this.channel.topic,
event: this.event,
payload: this.payload(),
ref: this.ref,
join_ref: this.channel.joinRef()
});
}
/**
*
* @param {*} status
* @param {*} callback
*/
receive(status, callback) {
if (this.hasReceived(status)) {
callback(this.receivedResp.response);
}
this.recHooks.push({ status, callback });
return this;
}
/**
* @private
*/
reset() {
this.cancelRefEvent();
this.ref = null;
this.refEvent = null;
this.receivedResp = null;
this.sent = false;
}
/**
* @private
*/
matchReceive({ status, response, _ref }) {
this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));
}
/**
* @private
*/
cancelRefEvent() {
if (!this.refEvent) {
return;
}
this.channel.off(this.refEvent);
}
/**
* @private
*/
cancelTimeout() {
clearTimeout(this.timeoutTimer);
this.timeoutTimer = null;
}
/**
* @private
*/
startTimeout() {
if (this.timeoutTimer) {
this.cancelTimeout();
}
this.ref = this.channel.socket.makeRef();
this.refEvent = this.channel.replyEventName(this.ref);
this.channel.on(this.refEvent, (payload) => {
this.cancelRefEvent();
this.cancelTimeout();
this.receivedResp = payload;
this.matchReceive(payload);
});
this.timeoutTimer = setTimeout(() => {
this.trigger("timeout", {});
}, this.timeout);
}
/**
* @private
*/
hasReceived(status) {
return this.receivedResp && this.receivedResp.status === status;
}
/**
* @private
*/
trigger(status, response) {
this.channel.trigger(this.refEvent, { status, response });
}
};
var Timer = class {
constructor(callback, timerCalc) {
this.callback = callback;
this.timerCalc = timerCalc;
this.timer = null;
this.tries = 0;
}
reset() {
this.tries = 0;
clearTimeout(this.timer);
}
/**
* Cancels any previous scheduleTimeout and schedules callback
*/
scheduleTimeout() {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.tries = this.tries + 1;
this.callback();
}, this.timerCalc(this.tries + 1));
}
};
var Channel = class {
constructor(topic, params, socket) {
this.state = CHANNEL_STATES.closed;
this.topic = topic;
this.params = closure(params || {});
this.socket = socket;
this.bindings = [];
this.bindingRef = 0;
this.timeout = this.socket.timeout;
this.joinedOnce = false;
this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
this.pushBuffer = [];
this.stateChangeRefs = [];
this.rejoinTimer = new Timer(() => {
if (this.socket.isConnected()) {
this.rejoin();
}
}, this.socket.rejoinAfterMs);
this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));
this.stateChangeRefs.push(
this.socket.onOpen(() => {
this.rejoinTimer.reset();
if (this.isErrored()) {
this.rejoin();
}
})
);
this.joinPush.receive("ok", () => {
this.state = CHANNEL_STATES.joined;
this.rejoinTimer.reset();
this.pushBuffer.forEach((pushEvent) => pushEvent.send());
this.pushBuffer = [];
});
this.joinPush.receive("error", () => {
this.state = CHANNEL_STATES.errored;
if (this.socket.isConnected()) {
this.rejoinTimer.scheduleTimeout();
}
});
this.onClose(() => {
this.rejoinTimer.reset();
if (this.socket.hasLogger()) this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`);
this.state = CHANNEL_STATES.closed;
this.socket.remove(this);
});
this.onError((reason) => {
if (this.socket.hasLogger()) this.socket.log("channel", `error ${this.topic}`, reason);
if (this.isJoining()) {
this.joinPush.reset();
}
this.state = CHANNEL_STATES.errored;
if (this.socket.isConnected()) {
this.rejoinTimer.scheduleTimeout();
}
});
this.joinPush.receive("timeout", () => {
if (this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);
let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);
leavePush.send();
this.state = CHANNEL_STATES.errored;
this.joinPush.reset();
if (this.socket.isConnected()) {
this.rejoinTimer.scheduleTimeout();
}
});
this.on(CHANNEL_EVENTS.reply, (payload, ref) => {
this.trigger(this.replyEventName(ref), payload);
});
}
/**
* Join the channel
* @param {integer} timeout
* @returns {Push}
*/
join(timeout = this.timeout) {
if (this.joinedOnce) {
throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");
} else {
this.timeout = timeout;
this.joinedOnce = true;
this.rejoin();
return this.joinPush;
}
}
/**
* Hook into channel close
* @param {Function} callback
*/
onClose(callback) {
this.on(CHANNEL_EVENTS.close, callback);
}
/**
* Hook into channel errors
* @param {Function} callback
*/
onError(callback) {
return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason));
}
/**
* Subscribes on channel events
*
* Subscription returns a ref counter, which can be used later to
* unsubscribe the exact event listener
*
* @example
* const ref1 = channel.on("event", do_stuff)
* const ref2 = channel.on("event", do_other_stuff)
* channel.off("event", ref1)
* // Since unsubscription, do_stuff won't fire,
* // while do_other_stuff will keep firing on the "event"
*
* @param {string} event
* @param {Function} callback
* @returns {integer} ref
*/
on(event, callback) {
let ref = this.bindingRef++;
this.bindings.push({ event, ref, callback });
return ref;
}
/**
* Unsubscribes off of channel events
*
* Use the ref returned from a channel.on() to unsubscribe one
* handler, or pass nothing for the ref to unsubscribe all
* handlers for the given event.
*
* @example
* // Unsubscribe the do_stuff handler
* const ref1 = channel.on("event", do_stuff)
* channel.off("event", ref1)
*
* // Unsubscribe all handlers from event
* channel.off("event")
*
* @param {string} event
* @param {integer} ref
*/
off(event, ref) {
this.bindings = this.bindings.filter((bind) => {
return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref));
});
}
/**
* @private
*/
canPush() {
return this.socket.isConnected() && this.isJoined();
}
/**
* Sends a message `event` to phoenix with the payload `payload`.
* Phoenix receives this in the `handle_in(event, payload, socket)`
* function. if phoenix replies or it times out (default 10000ms),
* then optionally the reply can be received.
*
* @example
* channel.push("event")
* .receive("ok", payload => console.log("phoenix replied:", payload))
* .receive("error", err => console.log("phoenix errored", err))
* .receive("timeout", () => console.log("timed out pushing"))
* @param {string} event
* @param {Object} payload
* @param {number} [timeout]
* @returns {Push}
*/
push(event, payload, timeout = this.timeout) {
payload = payload || {};
if (!this.joinedOnce) {
throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);
}
let pushEvent = new Push(this, event, function() {
return payload;
}, timeout);
if (this.canPush()) {
pushEvent.send();
} else {
pushEvent.startTimeout();
this.pushBuffer.push(pushEvent);
}
return pushEvent;
}
/** Leaves the channel
*
* Unsubscribes from server events, and
* instructs channel to terminate on server
*
* Triggers onClose() hooks
*
* To receive leave acknowledgements, use the `receive`
* hook to bind to the server ack, ie:
*
* @example
* channel.leave().receive("ok", () => alert("left!") )
*
* @param {integer} timeout
* @returns {Push}
*/
leave(timeout = this.timeout) {
this.rejoinTimer.reset();
this.joinPush.cancelTimeout();
this.state = CHANNEL_STATES.leaving;
let onClose = () => {
if (this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`);
this.trigger(CHANNEL_EVENTS.close, "leave");
};
let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);
leavePush.receive("ok", () => onClose()).receive("timeout", () => onClose());
leavePush.send();
if (!this.canPush()) {
leavePush.trigger("ok", {});
}
return leavePush;
}
/**
* Overridable message hook
*
* Receives all events for specialized message handling
* before dispatching to the channel callbacks.
*
* Must return the payload, modified or unmodified
* @param {string} event
* @param {Object} payload
* @param {integer} ref
* @returns {Object}
*/
onMessage(_event, payload, _ref) {
return payload;
}
/**
* @private
*/
isMember(topic, event, payload, joinRef) {
if (this.topic !== topic) {
return false;
}
if (joinRef && joinRef !== this.joinRef()) {
if (this.socket.hasLogger()) this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef });
return false;
} else {
return true;
}
}
/**
* @private
*/
joinRef() {
return this.joinPush.ref;
}
/**
* @private
*/
rejoin(timeout = this.timeout) {
if (this.isLeaving()) {
return;
}
this.socket.leaveOpenTopic(this.topic);
this.state = CHANNEL_STATES.joining;
this.joinPush.resend(timeout);
}
/**
* @private
*/
trigger(event, payload, ref, joinRef) {
let handledPayload = this.onMessage(event, payload, ref, joinRef);
if (payload && !handledPayload) {
throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");
}
let eventBindings = this.bindings.filter((bind) => bind.event === event);
for (let i = 0; i < eventBindings.length; i++) {
let bind = eventBindings[i];
bind.callback(handledPayload, ref, joinRef || this.joinRef());
}
}
/**
* @private
*/
replyEventName(ref) {
return `chan_reply_${ref}`;
}
/**
* @private
*/
isClosed() {
return this.state === CHANNEL_STATES.closed;
}
/**
* @private
*/
isErrored() {
return this.state === CHANNEL_STATES.errored;
}
/**
* @private
*/
isJoined() {
return this.state === CHANNEL_STATES.joined;
}
/**
* @private
*/
isJoining() {
return this.state === CHANNEL_STATES.joining;
}
/**
* @private
*/
isLeaving() {
return this.state === CHANNEL_STATES.leaving;
}
};
var Ajax = class {
static request(method, endPoint, headers, body, timeout, ontimeout, callback) {
if (global.XDomainRequest) {
let req = new global.XDomainRequest();
return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
} else if (global.XMLHttpRequest) {
let req = new global.XMLHttpRequest();
return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback);
} else if (global.fetch && global.AbortController) {
return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback);
} else {
throw new Error("No suitable XMLHttpRequest implementation found");
}
}
static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) {
let options = {
method,
headers,
body
};
let controller = null;
if (timeout) {
controller = new AbortController();
const _timeoutId = setTimeout(() => controller.abort(), timeout);
options.signal = controller.signal;
}
global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => {
if (err.name === "AbortError" && ontimeout) {
ontimeout();
} else {
callback && callback(null);
}
});
return controller;
}
static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
req.timeout = timeout;
req.open(method, endPoint);
req.onload = () => {
let response = this.parseJSON(req.responseText);
callback && callback(response);
};
if (ontimeout) {
req.ontimeout = ontimeout;
}
req.onprogress = () => {
};
req.send(body);
return req;
}
static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) {
req.open(method, endPoint, true);
req.timeout = timeout;
for (let [key, value] of Object.entries(headers)) {
req.setRequestHeader(key, value);
}
req.onerror = () => callback && callback(null);
req.onreadystatechange = () => {
if (req.readyState === XHR_STATES.complete && callback) {
let response = this.parseJSON(req.responseText);
callback(response);
}
};
if (ontimeout) {
req.ontimeout = ontimeout;
}
req.send(body);
return req;
}
static parseJSON(resp) {
if (!resp || resp === "") {
return null;
}
try {
return JSON.parse(resp);
} catch {
console && console.log("failed to parse JSON response", resp);
return null;
}
}
static serialize(obj, parentKey) {
let queryStr = [];
for (var key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
continue;
}
let paramKey = parentKey ? `${parentKey}[${key}]` : key;
let paramVal = obj[key];
if (typeof paramVal === "object") {
queryStr.push(this.serialize(paramVal, paramKey));
} else {
queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
}
}
return queryStr.join("&");
}
static appendParams(url, params) {
if (Object.keys(params).length === 0) {
return url;
}
let prefix = url.match(/\?/) ? "&" : "?";
return `${url}${prefix}${this.serialize(params)}`;
}
};
var arrayBufferToBase64 = (buffer) => {
let binary = "";
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
};
var LongPoll = class {
constructor(endPoint, protocols) {
if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) {
this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length));
}
this.endPoint = null;
this.token = null;
this.skipHeartbeat = true;
this.reqs = /* @__PURE__ */ new Set();
this.awaitingBatchAck = false;
this.currentBatch = null;
this.currentBatchTimer = null;
this.batchBuffer = [];
this.onopen = function() {
};
this.onerror = function() {
};
this.onmessage = function() {
};
this.onclose = function() {
};
this.pollEndpoint = this.normalizeEndpoint(endPoint);
this.readyState = SOCKET_STATES.connecting;
setTimeout(() => this.poll(), 0);
}
normalizeEndpoint(endPoint) {
return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
}
endpointURL() {
return Ajax.appendParams(this.pollEndpoint, { token: this.token });
}
closeAndRetry(code, reason, wasClean) {
this.close(code, reason, wasClean);
this.readyState = SOCKET_STATES.connecting;
}
ontimeout() {
this.onerror("timeout");
this.closeAndRetry(1005, "timeout", false);
}
isActive() {
return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting;
}
poll() {
const headers = { "Accept": "application/json" };
if (this.authToken) {
headers["X-Phoenix-AuthToken"] = this.authToken;
}
this.ajax("GET", headers, null, () => this.ontimeout(), (resp) => {
if (resp) {
var { status, token, messages } = resp;
if (status === 410 && this.token !== null) {
this.onerror(410);
this.closeAndRetry(3410, "session_gone", false);
return;
}
this.token = token;
} else {
status = 0;
}
switch (status) {
case 200:
messages.forEach((msg) => {
setTimeout(() => this.onmessage({ data: msg }), 0);
});
this.poll();
break;
case 204:
this.poll();
break;
case 410:
this.readyState = SOCKET_STATES.open;
this.onopen({});
this.poll();
break;
case 403:
this.onerror(403);
this.close(1008, "forbidden", false);
break;
case 0:
case 500:
this.onerror(500);
this.closeAndRetry(1011, "internal server error", 500);
break;
default:
throw new Error(`unhandled poll status ${status}`);
}
});
}
// we collect all pushes within the current event loop by
// setTimeout 0, which optimizes back-to-back procedural
// pushes against an empty buffer
send(body) {
if (typeof body !== "string") {
body = arrayBufferToBase64(body);
}
if (this.currentBatch) {
this.currentBatch.push(body);
} else if (this.awaitingBatchAck) {
this.batchBuffer.push(body);
} else {
this.currentBatch = [body];
this.currentBatchTimer = setTimeout(() => {
this.batchSend(this.currentBatch);
this.currentBatch = null;
}, 0);
}
}
batchSend(messages) {
this.awaitingBatchAck = true;
this.ajax("POST", { "Content-Type": "application/x-ndjson" }, messages.join("\n"), () => this.onerror("timeout"), (resp) => {
this.awaitingBatchAck = false;
if (!resp || resp.status !== 200) {
this.onerror(resp && resp.status);
this.closeAndRetry(1011, "internal server error", false);
} else if (this.batchBuffer.length > 0) {
this.batchSend(this.batchBuffer);
this.batchBuffer = [];
}
});
}
close(code, reason, wasClean) {
for (let req of this.reqs) {
req.abort();
}
this.readyState = SOCKET_STATES.closed;
let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean });
this.batchBuffer = [];
clearTimeout(this.currentBatchTimer);
this.currentBatchTimer = null;
if (typeof CloseEvent !== "undefined") {
this.onclose(new CloseEvent("close", opts));
} else {
this.onclose(opts);
}
}
ajax(method, headers, body, onCallerTimeout, callback) {
let req;
let ontimeout = () => {
this.reqs.delete(req);
onCallerTimeout();
};
req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => {
this.reqs.delete(req);
if (this.isActive()) {
callback(resp);
}
});
this.reqs.add(req);
}
};
var serializer_default = {
HEADER_LENGTH: 1,
META_LENGTH: 4,
KINDS: { push: 0, reply: 1, broadcast: 2 },
encode(msg, callback) {
if (msg.payload.constructor === ArrayBuffer) {
return callback(this.binaryEncode(msg));
} else {
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
return callback(JSON.stringify(payload));
}
},
decode(rawPayload, callback) {
if (rawPayload.constructor === ArrayBuffer) {
return callback(this.binaryDecode(rawPayload));
} else {
let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);
return callback({ join_ref, ref, topic, event, payload });
}
},
// private
binaryEncode(message) {
let { join_ref, ref, event, topic, payload } = message;
let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length;
let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);
let view = new DataView(header);
let offset = 0;
view.setUint8(offset++, this.KINDS.push);
view.setUint8(offset++, join_ref.length);
view.setUint8(offset++, ref.length);
view.setUint8(offset++, topic.length);
view.setUint8(offset++, event.length);
Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));
Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));
var combined = new Uint8Array(header.byteLength + payload.byteLength);
combined.set(new Uint8Array(header), 0);
combined.set(new Uint8Array(payload), header.byteLength);
return combined.buffer;
},
binaryDecode(buffer) {
let view = new DataView(buffer);
let kind = view.getUint8(0);
let decoder = new TextDecoder();
switch (kind) {
case this.KINDS.push:
return this.decodePush(buffer, view, decoder);
case this.KINDS.reply:
return this.decodeReply(buffer, view, decoder);
case this.KINDS.broadcast:
return this.decodeBroadcast(buffer, view, decoder);
}
},
decodePush(buffer, view, decoder) {
let joinRefSize = view.getUint8(1);
let topicSize = view.getUint8(2);
let eventSize = view.getUint8(3);
let offset = this.HEADER_LENGTH + this.META_LENGTH - 1;
let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));
offset = offset + joinRefSize;
let topic = decoder.decode(buffer.slice(offset, offset + topicSize));
offset = offset + topicSize;
let event = decoder.decode(buffer.slice(offset, offset + eventSize));
offset = offset + eventSize;
let data = buffer.slice(offset, buffer.byteLength);
return { join_ref: joinRef, ref: null, topic, event, payload: data };
},
decodeReply(buffer, view, decoder) {
let joinRefSize = view.getUint8(1);
let refSize = view.getUint8(2);
let topicSize = view.getUint8(3);
let eventSize = view.getUint8(4);
let offset = this.HEADER_LENGTH + this.META_LENGTH;
let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));
offset = offset + joinRefSize;
let ref = decoder.decode(buffer.slice(offset, offset + refSize));
offset = offset + refSize;
let topic = decoder.decode(buffer.slice(offset, offset + topicSize));
offset = offset + topicSize;
let event = decoder.decode(buffer.slice(offset, offset + eventSize));
offset = offset + eventSize;
let data = buffer.slice(offset, buffer.byteLength);
let payload = { status: event, response: data };
return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload };
},
decodeBroadcast(buffer, view, decoder) {
let topicSize = view.getUint8(1);
let eventSize = view.getUint8(2);
let offset = this.HEADER_LENGTH + 2;
let topic = decoder.decode(buffer.slice(offset, offset + topicSize));
offset = offset + topicSize;
let event = decoder.decode(buffer.slice(offset, offset + eventSize));
offset = offset + eventSize;
let data = buffer.slice(offset, buffer.byteLength);
return { join_ref: null, ref: null, topic, event, payload: data };
}
};
var Socket = class {
constructor(endPoint, opts = {}) {
this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
this.channels = [];
this.sendBuffer = [];
this.ref = 0;
this.fallbackRef = null;
this.timeout = opts.timeout || DEFAULT_TIMEOUT;
this.transport = opts.transport || global.WebSocket || LongPoll;
this.primaryPassedHealthCheck = false;
this.longPollFallbackMs = opts.longPollFallbackMs;
this.fallbackTimer = null;
this.sessionStore = opts.sessionStorage || global && global.sessionStorage;
this.establishedConnections = 0;
this.defaultEncoder = serializer_default.encode.bind(serializer_default);
this.defaultDecoder = serializer_default.decode.bind(serializer_default);
this.closeWasClean = true;
this.disconnecting = false;
this.binaryType = opts.binaryType || "arraybuffer";
this.connectClock = 1;
this.pageHidden = false;
if (this.transport !== LongPoll) {
this.encode = opts.encode || this.defaultEncoder;
this.decode = opts.decode || this.defaultDecoder;
} else {
this.encode = this.defaultEncoder;
this.decode = this.defaultDecoder;
}
let awaitingConnectionOnPageShow = null;
if (phxWindow && phxWindow.addEventListener) {
phxWindow.addEventListener("pagehide", (_e) => {
if (this.conn) {
this.disconnect();
awaitingConnectionOnPageShow = this.connectClock;
}
});
phxWindow.addEventListener("pageshow", (_e) => {
if (awaitingConnectionOnPageShow === this.connectClock) {
awaitingConnectionOnPageShow = null;
this.connect();
}
});
phxWindow.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
this.pageHidden = true;
} else {
this.pageHidden = false;
if (!this.isConnected() && !this.closeWasClean) {
this.teardown(() => this.connect());
}
}
});
}
this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4;
this.rejoinAfterMs = (tries) => {
if (opts.rejoinAfterMs) {
return opts.rejoinAfterMs(tries);
} else {
return [1e3, 2e3, 5e3][tries - 1] || 1e4;
}
};
this.reconnectAfterMs = (tries) => {
if (opts.reconnectAfterMs) {
return opts.reconnectAfterMs(tries);
} else {
return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3;
}
};
this.logger = opts.logger || null;
if (!this.logger && opts.debug) {
this.logger = (kind, msg, data) => {
console.log(`${kind}: ${msg}`, data);
};
}
this.longpollerTimeout = opts.longpollerTimeout || 2e4;
this.params = closure(opts.params || {});
this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;
this.vsn = opts.vsn || DEFAULT_VSN;
this.heartbeatTimeoutTimer = null;
this.heartbeatTimer = null;
this.pendingHeartbeatRef = null;
this.reconnectTimer = new Timer(() => {
if (this.pageHidden) {
this.log("Not reconnecting as page is hidden!");
this.teardown();
return;
}
this.teardown(() => this.connect());
}, this.reconnectAfterMs);
this.authToken = opts.authToken;
}
/**
* Returns the LongPoll transport reference
*/
getLongPollTransport() {
return LongPoll;
}
/**
* Disconnects and replaces the active transport
*
* @param {Function} newTransport - The new transport class to instantiate
*
*/
replaceTransport(newTransport) {
this.connectClock++;
this.closeWasClean = true;
clearTimeout(this.fallbackTimer);
this.reconnectTimer.reset();
if (this.conn) {
this.conn.close();
this.conn = null;
}
this.transport = newTransport;
}
/**
* Returns the socket protocol
*
* @returns {string}
*/
protocol() {
return location.protocol.match(/^https/) ? "wss" : "ws";
}
/**
* The fully qualified socket url
*
* @returns {string}
*/
endPointURL() {
let uri = Ajax.appendParams(
Ajax.appendParams(this.endPoint, this.params()),
{ vsn: this.vsn }
);
if (uri.charAt(0) !== "/") {
return uri;
}
if (uri.charAt(1) === "/") {
return `${this.protocol()}:${uri}`;
}
return `${this.protocol()}://${location.host}${uri}`;
}
/**
* Disconnects the socket
*
* See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.
*
* @param {Function} callback - Optional callback which is called after socket is disconnected.
* @param {integer} code - A status code for disconnection (Optional).
* @param {string} reason - A textual description of the reason to disconnect. (Optional)
*/
disconnect(callback, code, reason) {
this.connectClock++;
this.disconnecting = true;
this.closeWasClean = true;
clearTimeout(this.fallbackTimer);
this.reconnectTimer.reset();
this.teardown(() => {
this.disconnecting = false;
callback && callback();
}, code, reason);
}
/**
*
* @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
*
* Passing params to connect is deprecated; pass them in the Socket constructor instead:
* `new Socket("/socket", {params: {user_id: userToken}})`.
*/
connect(params) {
if (params) {
console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
this.params = closure(params);
}
if (this.conn && !this.disconnecting) {
return;
}
if (this.longPollFallbackMs && this.transport !== LongPoll) {
this.connectWithFallback(LongPoll, this.longPollFallbackMs);
} else {
this.transportConnect();
}
}
/**
* Logs the message. Override `this.logger` for specialized logging. noops by default
* @param {string} kind
* @param {string} msg
* @param {Object} data
*/
log(kind, msg, data) {
this.logger && this.logger(kind, msg, data);
}
/**
* Returns true if a logger has been set on this socket.
*/
hasLogger() {
return this.logger !== null;
}
/**
* Registers callbacks for connection open events
*
* @example socket.onOpen(function(){ console.info("the socket was opened") })
*
* @param {Function} callback
*/
onOpen(callback) {
let ref = this.makeRef();
this.stateChangeCallbacks.open.push([ref, callback]);
return ref;
}
/**
* Registers callbacks for connection close events
* @param {Function} callback
*/
onClose(callback) {
let ref = this.makeRef();
this.stateChangeCallbacks.close.push([ref, callback]);
return ref;
}
/**
* Registers callbacks for connection error events
*
* @example socket.onError(function(error){ alert("An error occurred") })
*
* @param {Function} callback
*/
onError(callback) {
let ref = this.makeRef();
this.stateChangeCallbacks.error.push([ref, callback]);
return ref;
}
/**
* Registers callbacks for connection message events
* @param {Function} callback
*/
onMessage(callback) {
let ref = this.makeRef();
this.stateChangeCallbacks.message.push([ref, callback]);
return ref;
}
/**
* Pings the server and invokes the callback with the RTT in milliseconds
* @param {Function} callback
*
* Returns true if the ping was pushed or false if unable to be pushed.
*/
ping(callback) {
if (!this.isConnected()) {
return false;
}
let ref = this.makeRef();
let startTime = Date.now();
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref });
let onMsgRef = this.onMessage((msg) => {
if (msg.ref === ref) {
this.off([onMsgRef]);
callback(Date.now() - startTime);
}
});
return true;
}
/**
* @private
*
* @param {Function}
*/
transportName(transport) {
switch (transport) {
case LongPoll:
return "LongPoll";
default:
return transport.name;
}
}
/**
* @private
*/
transportConnect() {
this.connectClock++;
this.closeWasClean = false;
let protocols = void 0;
if (this.authToken) {
protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`];
}
this.conn = new this.transport(this.endPointURL(), protocols);
this.conn.binaryType = this.binaryType;
this.conn.timeout = this.longpollerTimeout;
this.conn.onopen = () => this.onConnOpen();
this.conn.onerror = (error) => this.onConnError(error);
this.conn.onmessage = (event) => this.onConnMessage(event);
this.conn.onclose = (event) => this.onConnClose(event);
}
getSession(key) {
return this.sessionStore && this.sessionStore.getItem(key);
}
storeSession(key, val) {
this.sessionStore && this.sessionStore.setItem(key, val);
}
connectWithFallback(fallbackTransport, fallbackThreshold = 2500) {
clearTimeout(this.fallbackTimer);
let established = false;
let primaryTransport = true;
let openRef, errorRef;
let fallbackTransportName = this.transportName(fallbackTransport);
let fallback = (reason) => {
this.log("transport", `falling back to ${fallbackTransportName}...`, reason);
this.off([openRef, errorRef]);
primaryTransport = false;
this.replaceTransport(fallbackTransport);
this.transportConnect();
};
if (this.getSession(`phx:fallback:${fallbackTransportName}`)) {
return fallback("memorized");
}
this.fallbackTimer = setTimeout(fallback, fallbackThreshold);
errorRef = this.onError((reason) => {
this.log("transport", "error", reason);
if (primaryTransport && !established) {
clearTimeout(this.fallbackTimer);
fallback(reason);
}
});
if (this.fallbackRef) {
this.off([this.fallbackRef]);
}
this.fallbackRef = this.onOpen(() => {
established = true;
if (!primaryTransport) {
let fallbackTransportName2 = this.transportName(fallbackTransport);
if (!this.primaryPassedHealthCheck) {
this.storeSession(`phx:fallback:${fallbackTransportName2}`, "true");
}
return this.log("transport", `established ${fallbackTransportName2} fallback`);
}
clearTimeout(this.fallbackTimer);
this.fallbackTimer = setTimeout(fallback, fallbackThreshold);
this.ping((rtt) => {
this.log("transport", "connected to primary after", rtt);
this.primaryPassedHealthCheck = true;
clearTimeout(this.fallbackTimer);
});
});
this.transportConnect();
}
clearHeartbeats() {
clearTimeout(this.heartbeatTimer);
clearTimeout(this.heartbeatTimeoutTimer);
}
onConnOpen() {
if (this.hasLogger()) this.log("transport", `${this.transportName(this.transport)} connected to ${this.endPointURL()}`);
this.closeWasClean = false;
this.disconnecting = false;
this.establishedConnections++;
this.flushSendBuffer();
this.reconnectTimer.reset();
this.resetHeartbeat();
this.stateChangeCallbacks.open.forEach(([, callback]) => callback());
}
/**
* @private
*/
heartbeatTimeout() {
if (this.pendingHeartbeatRef) {
this.pendingHeartbeatRef = null;
if (this.hasLogger()) {
this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
}
this.triggerChanError();
this.closeWasClean = false;
this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, "heartbeat timeout");
}
}
resetHeartbeat() {
if (this.conn && this.conn.skipHeartbeat) {
return;
}
this.pendingHeartbeatRef = null;
this.clearHeartbeats();
this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
}
teardown(callback, code, reason) {
if (!this.conn) {
return callback && callback();
}
const connToClose = this.conn;
this.waitForBufferDone(connToClose, () => {
if (code) {
connToClose.close(code, reason || "");
} else {
connToClose.close();
}
this.waitForSocketClosed(connToClose, () => {
if (this.conn === connToClose) {
this.conn.onopen = function() {
};
this.conn.onerror = function() {
};
this.conn.onmessage = function() {
};
this.conn.onclose = function() {
};
this.conn = null;
}
callback && callback();
});
});
}
waitForBufferDone(conn, callback, tries = 1) {
if (tries === 5 || !conn.bufferedAmount) {
callback();
return;
}
setTimeout(() => {
this.waitForBufferDone(conn, callback, tries + 1);
}, 150 * tries);
}
waitForSocketClosed(conn, callback, tries = 1) {
if (tries === 5 || conn.readyState === SOCKET_STATES.closed) {
callback();
return;
}
setTimeout(() => {
this.waitForSocketClosed(conn, callback, tries + 1);
}, 150 * tries);
}
onConnClose(event) {
if (this.conn) this.conn.onclose = () => {
};
let closeCode = event && event.code;
if (this.hasLogger()) this.log("transport", "close", event);
this.triggerChanError();
this.clearHeartbeats();
if (!this.closeWasClean && closeCode !== 1e3) {
this.reconnectTimer.scheduleTimeout();
}
this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));
}
/**
* @private
*/
onConnError(error) {
if (this.hasLogger()) this.log("transport", error);
let transportBefore = this.transport;
let establishedBefore = this.establishedConnections;
this.stateChangeCallbacks.error.forEach(([, callback]) => {
callback(error, transportBefore, establishedBefore);
});
if (transportBefore === this.transport || establishedBefore > 0) {
this.triggerChanError();
}
}
/**
* @private
*/
triggerChanError() {
this.channels.forEach((channel) => {
if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {
channel.trigger(CHANNEL_EVENTS.error);
}
});
}
/**
* @returns {string}
*/
connectionState() {
switch (this.conn && this.conn.readyState) {
case SOCKET_STATES.connecting:
return "connecting";
case SOCKET_STATES.open:
return "open";
case SOCKET_STATES.closing:
return "closing";
default:
return "closed";
}
}
/**
* @returns {boolean}
*/
isConnected() {
return this.connectionState() === "open";
}
/**
* @private
*
* @param {Channel}
*/
remove(channel) {
this.off(channel.stateChangeRefs);
this.channels = this.channels.filter((c) => c !== channel);
}
/**
* Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.
*
* @param {refs} - list of refs returned by calls to
* `onOpen`, `onClose`, `onError,` and `onMessage`
*/
off(refs) {
for (let key in this.stateChangeCallbacks) {
this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {
return refs.indexOf(ref) === -1;
});
}
}
/**
* Initiates a new channel for the given topic
*
* @param {string} topic
* @param {Object} chanParams - Parameters for the channel
* @returns {Channel}
*/
channel(topic, chanParams = {}) {
let chan = new Channel(topic, chanParams, this);
this.channels.push(chan);
return chan;
}
/**
* @param {Object} data
*/
push(data) {
if (this.hasLogger()) {
let { topic, event, payload, ref, join_ref } = data;
this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload);
}
if (this.isConnected()) {
this.encode(data, (result) => this.conn.send(result));
} else {
this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result)));
}
}
/**
* Return the next message ref, accounting for overflows
* @returns {string}
*/
makeRef() {
let newRef = this.ref + 1;
if (newRef === this.ref) {
this.ref = 0;
} else {
this.ref = newRef;
}
return this.ref.toString();
}
sendHeartbeat() {
if (this.pendingHeartbeatRef && !this.isConnected()) {
return;
}
this.pendingHeartbeatRef = this.makeRef();
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs);
}
flushSendBuffer() {
if (this.isConnected() && this.sendBuffer.length > 0) {
this.sendBuffer.forEach((callback) => callback());
this.sendBuffer = [];
}
}
onConnMessage(rawMessage) {
this.decode(rawMessage.data, (msg) => {
let { topic, event, payload, ref, join_ref } = msg;
if (ref && ref === this.pendingHeartbeatRef) {
this.clearHeartbeats();
this.pendingHeartbeatRef = null;
this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
}
if (this.hasLogger()) this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload);
for (let i = 0; i < this.channels.length; i++) {
const channel = this.channels[i];
if (!channel.isMember(topic, event, payload, join_ref)) {
continue;
}
channel.trigger(event, payload, ref, join_ref);
}
for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {
let [, callback] = this.stateChangeCallbacks.message[i];
callback(msg);
}
});
}
leaveOpenTopic(topic) {
let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining()));
if (dupChannel) {
if (this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`);
dupChannel.leave();
}
}
};
// ../deps/phoenix_live_view/priv/static/phoenix_live_view.esm.js
var CONSECUTIVE_RELOADS = "consecutive-reloads";
var MAX_RELOADS = 10;
var RELOAD_JITTER_MIN = 5e3;
var RELOAD_JITTER_MAX = 1e4;
var FAILSAFE_JITTER = 3e4;
var PHX_EVENT_CLASSES = [
"phx-click-loading",
"phx-change-loading",
"phx-submit-loading",
"phx-keydown-loading",
"phx-keyup-loading",
"phx-blur-loading",
"phx-focus-loading",
"phx-hook-loading"
];
var PHX_DROP_TARGET_ACTIVE_CLASS = "phx-drop-target-active";
var PHX_COMPONENT = "data-phx-component";
var PHX_VIEW_REF = "data-phx-view";
var PHX_LIVE_LINK = "data-phx-link";
var PHX_TRACK_STATIC = "track-static";
var PHX_LINK_STATE = "data-phx-link-state";
var PHX_REF_LOADING = "data-phx-ref-loading";
var PHX_REF_SRC = "data-phx-ref-src";
var PHX_REF_LOCK = "data-phx-ref-lock";
var PHX_PENDING_REFS = "phx-pending-refs";
var PHX_TRACK_UPLOADS = "track-uploads";
var PHX_UPLOAD_REF = "data-phx-upload-ref";
var PHX_PREFLIGHTED_REFS = "data-phx-preflighted-refs";
var PHX_DONE_REFS = "data-phx-done-refs";
var PHX_DROP_TARGET = "drop-target";
var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
var PHX_SKIP = "data-phx-skip";
var PHX_MAGIC_ID = "data-phx-id";
var PHX_PRUNE = "data-phx-prune";
var PHX_CONNECTED_CLASS = "phx-connected";
var PHX_LOADING_CLASS = "phx-loading";
var PHX_ERROR_CLASS = "phx-error";
var PHX_CLIENT_ERROR_CLASS = "phx-client-error";
var PHX_SERVER_ERROR_CLASS = "phx-server-error";
var PHX_PARENT_ID = "data-phx-parent-id";
var PHX_MAIN = "data-phx-main";
var PHX_ROOT_ID = "data-phx-root-id";
var PHX_VIEWPORT_TOP = "viewport-top";
var PHX_VIEWPORT_BOTTOM = "viewport-bottom";
var PHX_VIEWPORT_OVERRUN_TARGET = "viewport-overrun-target";
var PHX_TRIGGER_ACTION = "trigger-action";
var PHX_HAS_FOCUSED = "phx-has-focused";
var FOCUSABLE_INPUTS = [
"text",
"textarea",
"number",
"email",
"password",
"search",
"tel",
"url",
"date",
"time",
"datetime-local",
"color",
"range"
];
var CHECKABLE_INPUTS = ["checkbox", "radio"];
var PHX_HAS_SUBMITTED = "phx-has-submitted";
var PHX_SESSION = "data-phx-session";
var PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`;
var PHX_STICKY = "data-phx-sticky";
var PHX_STATIC = "data-phx-static";
var PHX_READONLY = "data-phx-readonly";
var PHX_DISABLED = "data-phx-disabled";
var PHX_DISABLE_WITH = "disable-with";
var PHX_DISABLE_WITH_RESTORE = "data-phx-disable-with-restore";
var PHX_HOOK = "hook";
var PHX_DEBOUNCE = "debounce";
var PHX_THROTTLE = "throttle";
var PHX_UPDATE = "update";
var PHX_STREAM = "stream";
var PHX_STREAM_REF = "data-phx-stream";
var PHX_PORTAL = "data-phx-portal";
var PHX_TELEPORTED_REF = "data-phx-teleported";
var PHX_TELEPORTED_SRC = "data-phx-teleported-src";
var PHX_RUNTIME_HOOK = "data-phx-runtime-hook";
var PHX_LV_PID = "data-phx-pid";
var PHX_KEY = "key";
var PHX_PRIVATE = "phxPrivate";
var PHX_AUTO_RECOVER = "auto-recover";
var PHX_LV_DEBUG = "phx:live-socket:debug";
var PHX_LV_PROFILE = "phx:live-socket:profiling";
var PHX_LV_LATENCY_SIM = "phx:live-socket:latency-sim";
var PHX_LV_HISTORY_POSITION = "phx:nav-history-position";
var PHX_PROGRESS = "progress";
var PHX_MOUNTED = "mounted";
var PHX_RELOAD_STATUS = "__phoenix_reload_status__";
var LOADER_TIMEOUT = 1;
var MAX_CHILD_JOIN_ATTEMPTS = 3;
var BEFORE_UNLOAD_LOADER_TIMEOUT = 200;
var DISCONNECTED_TIMEOUT = 500;
var BINDING_PREFIX = "phx-";
var PUSH_TIMEOUT = 3e4;
var DEBOUNCE_TRIGGER = "debounce-trigger";
var THROTTLED = "throttled";
var DEBOUNCE_PREV_KEY = "debounce-prev-key";
var DEFAULTS = {
debounce: 300,
throttle: 300
};
var PHX_PENDING_ATTRS = [PHX_REF_LOADING, PHX_REF_SRC, PHX_REF_LOCK];
var STATIC = "s";
var ROOT = "r";
var COMPONENTS = "c";
var KEYED = "k";
var KEYED_COUNT = "kc";
var EVENTS = "e";
var REPLY = "r";
var TITLE = "t";
var TEMPLATES = "p";
var STREAM = "stream";
var EntryUploader = class {
constructor(entry, config, liveSocket) {
const { chunk_size, chunk_timeout } = config;
this.liveSocket = liveSocket;
this.entry = entry;
this.offset = 0;
this.chunkSize = chunk_size;
this.chunkTimeout = chunk_timeout;
this.chunkTimer = null;
this.errored = false;
this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {
token: entry.metadata()
});
}
error(reason) {
if (this.errored) {
return;
}
this.uploadChannel.leave();
this.errored = true;
clearTimeout(this.chunkTimer);
this.entry.error(reason);
}
upload() {
this.uploadChannel.onError((reason) => this.error(reason));
this.uploadChannel.join().receive("ok", (_data) => this.readNextChunk()).receive("error", (reason) => this.error(reason));
}
isDone() {
return this.offset >= this.entry.file.size;
}
readNextChunk() {
const reader = new window.FileReader();
const blob = this.entry.file.slice(
this.offset,
this.chunkSize + this.offset
);
reader.onload = (e) => {
if (e.target.error === null) {
this.offset += /** @type {ArrayBuffer} */
e.target.result.byteLength;
this.pushChunk(
/** @type {ArrayBuffer} */
e.target.result
);
} else {
return logError("Read error: " + e.target.error);
}
};
reader.readAsArrayBuffer(blob);
}
pushChunk(chunk) {
if (!this.uploadChannel.isJoined()) {
return;
}
this.uploadChannel.push("chunk", chunk, this.chunkTimeout).receive("ok", () => {
this.entry.progress(this.offset / this.entry.file.size * 100);
if (!this.isDone()) {
this.chunkTimer = setTimeout(
() => this.readNextChunk(),
this.liveSocket.getLatencySim() || 0
);
}
}).receive("error", ({ reason }) => this.error(reason));
}
};
var logError = (msg, obj) => console.error && console.error(msg, obj);
var isCid = (cid) => {
const type = typeof cid;
return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
};
function detectDuplicateIds() {
const ids = /* @__PURE__ */ new Set();
const elems = document.querySelectorAll("*[id]");
for (let i = 0, len = elems.length; i < len; i++) {
if (ids.has(elems[i].id)) {
console.error(
`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`
);
} else {
ids.add(elems[i].id);
}
}
}
function detectInvalidStreamInserts(inserts) {
const errors = /* @__PURE__ */ 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));
}
var debug = (view, kind, msg, obj) => {
if (view.liveSocket.isDebugEnabled()) {
console.log(`${view.id} ${kind}: ${msg} - `, obj);
}
};
var closure2 = (val) => typeof val === "function" ? val : function() {
return val;
};
var clone = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
var closestPhxBinding = (el, binding, borderEl) => {
do {
if (el.matches(`[${binding}]`) && !el.disabled) {
return el;
}
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1 && !(borderEl && borderEl.isSameNode(el) || el.matches(PHX_VIEW_SELECTOR)));
return null;
};
var isObject = (obj) => {
return obj !== null && typeof obj === "object" && !(obj instanceof Array);
};
var isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2);
var isEmpty = (obj) => {
for (const x in obj) {
return false;
}
return true;
};
var maybe = (el, callback) => el && callback(el);
var channelUploader = function(entries, onError, resp, liveSocket) {
entries.forEach((entry) => {
const entryUploader = new EntryUploader(entry, resp.config, liveSocket);
entryUploader.upload();
});
};
var eventContainsFiles = (e) => {
if (e.dataTransfer.types) {
for (let i = 0; i < e.dataTransfer.types.length; i++) {
if (e.dataTransfer.types[i] === "Files") {
return true;
}
}
}
return false;
};
var Browser = {
canPushState() {
return typeof history.pushState !== "undefined";
},
dropLocal(localStorage, namespace, subkey) {
return localStorage.removeItem(this.localKey(namespace, subkey));
},
updateLocal(localStorage, namespace, subkey, initial, func) {
const current = this.getLocal(localStorage, namespace, subkey);
const key = this.localKey(namespace, subkey);
const newVal = current === null ? initial : func(current);
localStorage.setItem(key, JSON.stringify(newVal));
return newVal;
},
getLocal(localStorage, namespace, subkey) {
return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)));
},
updateCurrentState(callback) {
if (!this.canPushState()) {
return;
}
history.replaceState(
callback(history.state || {}),
"",
window.location.href
);
},
pushState(kind, meta, to) {
if (this.canPushState()) {
if (to !== window.location.href) {
if (meta.type == "redirect" && meta.scroll) {
const currentState = history.state || {};
currentState.scroll = meta.scroll;
history.replaceState(currentState, "", window.location.href);
}
delete meta.scroll;
history[kind + "State"](meta, "", to || null);
window.requestAnimationFrame(() => {
const hashEl = this.getHashTargetEl(window.location.hash);
if (hashEl) {
hashEl.scrollIntoView();
} else if (meta.type === "redirect") {
window.scroll(0, 0);
}
});
}
} else {
this.redirect(to);
}
},
setCookie(name, value, maxAgeSeconds) {
const expires = typeof maxAgeSeconds === "number" ? ` max-age=${maxAgeSeconds};` : "";
document.cookie = `${name}=${value};${expires} path=/`;
},
getCookie(name) {
return document.cookie.replace(
new RegExp(`(?:(?:^|.*;s*)${name}s*=s*([^;]*).*$)|^.*$`),
"$1"
);
},
deleteCookie(name) {
document.cookie = `${name}=; max-age=-1; path=/`;
},
redirect(toURL, flash, navigate = (url) => {
window.location.href = url;
}) {
if (flash) {
this.setCookie("__phoenix_flash__", flash, 60);
}
navigate(toURL);
},
localKey(namespace, subkey) {
return `${namespace}-${subkey}`;
},
getHashTargetEl(maybeHash) {
const hash = maybeHash.toString().substring(1);
if (hash === "") {
return;
}
return document.getElementById(hash) || document.querySelector(`a[name="${hash}"]`);
}
};
var browser_default = Browser;
var DOM = {
byId(id) {
return document.getElementById(id) || logError(`no id found for ${id}`);
},
removeClass(el, className) {
el.classList.remove(className);
if (el.classList.length === 0) {
el.removeAttribute("class");
}
},
all(node, query, callback) {
if (!node) {
return [];
}
const array = Array.from(node.querySelectorAll(query));
if (callback) {
array.forEach(callback);
}
return array;
},
childNodeLength(html) {
const template = document.createElement("template");
template.innerHTML = html;
return template.content.childElementCount;
},
isUploadInput(el) {
return el.type === "file" && el.getAttribute(PHX_UPLOAD_REF) !== null;
},
isAutoUpload(inputEl) {
return inputEl.hasAttribute("data-phx-auto-upload");
},
findUploadInputs(node) {
const formId = node.id;
const inputsOutsideForm = this.all(
document,
`input[type="file"][${PHX_UPLOAD_REF}][form="${formId}"]`
);
return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`).concat(
inputsOutsideForm
);
},
findComponentNodeList(viewId, cid, doc2 = document) {
return this.all(
doc2,
`[${PHX_VIEW_REF}="${viewId}"][${PHX_COMPONENT}="${cid}"]`
);
},
isPhxDestroyed(node) {
return node.id && DOM.private(node, "destroyed") ? true : false;
},
wantsNewTab(e) {
const wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
const isDownload = e.target instanceof HTMLAnchorElement && e.target.hasAttribute("download");
const isTargetBlank = e.target.hasAttribute("target") && e.target.getAttribute("target").toLowerCase() === "_blank";
const isTargetNamedTab = e.target.hasAttribute("target") && !e.target.getAttribute("target").startsWith("_");
return wantsNewTab || isTargetBlank || isDownload || isTargetNamedTab;
},
isUnloadableFormSubmit(e) {
const isDialogSubmit = e.target && e.target.getAttribute("method") === "dialog" || e.submitter && e.submitter.getAttribute("formmethod") === "dialog";
if (isDialogSubmit) {
return false;
} else {
return !e.defaultPrevented && !this.wantsNewTab(e);
}
},
isNewPageClick(e, currentLocation) {
const href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
let url;
if (e.defaultPrevented || href === null || this.wantsNewTab(e)) {
return false;
}
if (href.startsWith("mailto:") || href.startsWith("tel:")) {
return false;
}
if (e.target.isContentEditable) {
return false;
}
try {
url = new URL(href);
} catch {
try {
url = new URL(href, currentLocation);
} catch {
return true;
}
}
if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
return url.hash === "" && !url.href.endsWith("#");
}
}
return url.protocol.startsWith("http");
},
markPhxChildDestroyed(el) {
if (this.isPhxChild(el)) {
el.setAttribute(PHX_SESSION, "");
}
this.putPrivate(el, "destroyed", true);
},
findPhxChildrenInFragment(html, parentId) {
const template = document.createElement("template");
template.innerHTML = html;
return this.findPhxChildren(template.content, parentId);
},
isIgnored(el, phxUpdate) {
return (el.getAttribute(phxUpdate) || el.getAttribute("data-phx-update")) === "ignore";
},
isPhxUpdate(el, phxUpdate, updateTypes) {
return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
},
findPhxSticky(el) {
return this.all(el, `[${PHX_STICKY}]`);
},
findPhxChildren(el, parentId) {
return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
},
findExistingParentCIDs(viewId, cids) {
const parentCids = /* @__PURE__ */ new Set();
const childrenCids = /* @__PURE__ */ new Set();
cids.forEach((cid) => {
this.all(
document,
`[${PHX_VIEW_REF}="${viewId}"][${PHX_COMPONENT}="${cid}"]`
).forEach((parent) => {
parentCids.add(cid);
this.all(parent, `[${PHX_VIEW_REF}="${viewId}"][${PHX_COMPONENT}]`).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
});
});
childrenCids.forEach((childCid) => parentCids.delete(childCid));
return parentCids;
},
private(el, key) {
return el[PHX_PRIVATE] && el[PHX_PRIVATE][key];
},
deletePrivate(el, key) {
el[PHX_PRIVATE] && delete el[PHX_PRIVATE][key];
},
putPrivate(el, key, value) {
if (!el[PHX_PRIVATE]) {
el[PHX_PRIVATE] = {};
}
el[PHX_PRIVATE][key] = value;
},
updatePrivate(el, key, defaultVal, updateFunc) {
const existing = this.private(el, key);
if (existing === void 0) {
this.putPrivate(el, key, updateFunc(defaultVal));
} else {
this.putPrivate(el, key, updateFunc(existing));
}
},
syncPendingAttrs(fromEl, toEl) {
if (!fromEl.hasAttribute(PHX_REF_SRC)) {
return;
}
PHX_EVENT_CLASSES.forEach((className) => {
fromEl.classList.contains(className) && toEl.classList.add(className);
});
PHX_PENDING_ATTRS.filter((attr) => fromEl.hasAttribute(attr)).forEach(
(attr) => {
toEl.setAttribute(attr, fromEl.getAttribute(attr));
}
);
},
copyPrivates(target, source) {
if (source[PHX_PRIVATE]) {
target[PHX_PRIVATE] = source[PHX_PRIVATE];
}
},
putTitle(str) {
const titleEl = document.querySelector("title");
if (titleEl) {
const { prefix, suffix, default: defaultTitle } = titleEl.dataset;
const isEmpty2 = typeof str !== "string" || str.trim() === "";
if (isEmpty2 && typeof defaultTitle !== "string") {
return;
}
const inner = isEmpty2 ? defaultTitle : str;
document.title = `${prefix || ""}${inner || ""}${suffix || ""}`;
} else {
document.title = str;
}
},
debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback) {
let debounce = el.getAttribute(phxDebounce);
let throttle = el.getAttribute(phxThrottle);
if (debounce === "") {
debounce = defaultDebounce;
}
if (throttle === "") {
throttle = defaultThrottle;
}
const value = debounce || throttle;
switch (value) {
case null:
return callback();
case "blur":
this.incCycle(el, "debounce-blur-cycle", () => {
if (asyncFilter()) {
callback();
}
});
if (this.once(el, "debounce-blur")) {
el.addEventListener(
"blur",
() => this.triggerCycle(el, "debounce-blur-cycle")
);
}
return;
default:
const timeout = parseInt(value);
const trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback();
const currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger);
if (isNaN(timeout)) {
return logError(`invalid throttle/debounce value: ${value}`);
}
if (throttle) {
let newKeyDown = false;
if (event.type === "keydown") {
const prevKey = this.private(el, DEBOUNCE_PREV_KEY);
this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key);
newKeyDown = prevKey !== event.key;
}
if (!newKeyDown && this.private(el, THROTTLED)) {
return false;
} else {
callback();
const t = setTimeout(() => {
if (asyncFilter()) {
this.triggerCycle(el, DEBOUNCE_TRIGGER);
}
}, timeout);
this.putPrivate(el, THROTTLED, t);
}
} else {
setTimeout(() => {
if (asyncFilter()) {
this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle);
}
}, timeout);
}
const form = el.form;
if (form && this.once(form, "bind-debounce")) {
form.addEventListener("submit", () => {
Array.from(new FormData(form).entries(), ([name]) => {
const namedItem = form.elements.namedItem(name);
const input = namedItem instanceof RadioNodeList ? namedItem[0] : namedItem;
if (input) {
this.incCycle(input, DEBOUNCE_TRIGGER);
this.deletePrivate(input, THROTTLED);
}
});
});
}
if (this.once(el, "bind-debounce")) {
el.addEventListener("blur", () => {
clearTimeout(this.private(el, THROTTLED));
this.triggerCycle(el, DEBOUNCE_TRIGGER);
});
}
}
},
triggerCycle(el, key, currentCycle) {
const [cycle, trigger] = this.private(el, key);
if (!currentCycle) {
currentCycle = cycle;
}
if (currentCycle === cycle) {
this.incCycle(el, key);
trigger();
}
},
once(el, key) {
if (this.private(el, key) === true) {
return false;
}
this.putPrivate(el, key, true);
return true;
},
incCycle(el, key, trigger = function() {
}) {
let [currentCycle] = this.private(el, key) || [0, trigger];
currentCycle++;
this.putPrivate(el, key, [currentCycle, trigger]);
return currentCycle;
},
// maintains or adds privately used hook information
// fromEl and toEl can be the same element in the case of a newly added node
// fromEl and toEl can be any HTML node type, so we need to check if it's an element node
maintainPrivateHooks(fromEl, toEl, phxViewportTop, phxViewportBottom) {
if (fromEl.hasAttribute && fromEl.hasAttribute("data-phx-hook") && !toEl.hasAttribute("data-phx-hook")) {
toEl.setAttribute("data-phx-hook", fromEl.getAttribute("data-phx-hook"));
}
if (toEl.hasAttribute && (toEl.hasAttribute(phxViewportTop) || toEl.hasAttribute(phxViewportBottom))) {
toEl.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
}
},
putCustomElHook(el, hook) {
if (el.isConnected) {
el.setAttribute("data-phx-hook", "");
} else {
console.error(`
hook attached to non-connected DOM element
ensure you are calling createHook within your connectedCallback. ${el.outerHTML}
`);
}
this.putPrivate(el, "custom-el-hook", hook);
},
getCustomElHook(el) {
return this.private(el, "custom-el-hook");
},
isUsedInput(el) {
return el.nodeType === Node.ELEMENT_NODE && (this.private(el, PHX_HAS_FOCUSED) || this.private(el, PHX_HAS_SUBMITTED));
},
resetForm(form) {
Array.from(form.elements).forEach((input) => {
this.deletePrivate(input, PHX_HAS_FOCUSED);
this.deletePrivate(input, PHX_HAS_SUBMITTED);
});
},
isPhxChild(node) {
return node.getAttribute && node.getAttribute(PHX_PARENT_ID);
},
isPhxSticky(node) {
return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
},
isChildOfAny(el, parents) {
return !!parents.find((parent) => parent.contains(el));
},
firstPhxChild(el) {
return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
},
isPortalTemplate(el) {
return el.tagName === "TEMPLATE" && el.hasAttribute(PHX_PORTAL);
},
closestViewEl(el) {
const portalOrViewEl = el.closest(
`[${PHX_TELEPORTED_REF}],${PHX_VIEW_SELECTOR}`
);
if (!portalOrViewEl) {
return null;
}
if (portalOrViewEl.hasAttribute(PHX_TELEPORTED_REF)) {
return this.byId(portalOrViewEl.getAttribute(PHX_TELEPORTED_REF));
} else if (portalOrViewEl.hasAttribute(PHX_SESSION)) {
return portalOrViewEl;
}
return null;
},
dispatchEvent(target, name, opts = {}) {
let defaultBubble = true;
const isUploadTarget = target.nodeName === "INPUT" && target.type === "file";
if (isUploadTarget && name === "click") {
defaultBubble = false;
}
const bubbles = opts.bubbles === void 0 ? defaultBubble : !!opts.bubbles;
const eventOpts = {
bubbles,
cancelable: true,
detail: opts.detail || {}
};
const event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
target.dispatchEvent(event);
},
cloneNode(node, html) {
if (typeof html === "undefined") {
return node.cloneNode(true);
} else {
const cloned = node.cloneNode(false);
cloned.innerHTML = html;
return cloned;
}
},
// merge attributes from source to target
// if an element is ignored, we only merge data attributes
// including removing data attributes that are no longer in the source
mergeAttrs(target, source, opts = {}) {
const exclude = new Set(opts.exclude || []);
const isIgnored = opts.isIgnored;
const sourceAttrs = source.attributes;
for (let i = sourceAttrs.length - 1; i >= 0; i--) {
const name = sourceAttrs[i].name;
if (!exclude.has(name)) {
const sourceValue = source.getAttribute(name);
if (target.getAttribute(name) !== sourceValue && (!isIgnored || isIgnored && name.startsWith("data-"))) {
target.setAttribute(name, sourceValue);
}
} else {
if (name === "value") {
const sourceValue = source.value ?? source.getAttribute(name);
if (target.value === sourceValue) {
target.setAttribute("value", source.getAttribute(name));
}
}
}
}
const targetAttrs = target.attributes;
for (let i = targetAttrs.length - 1; i >= 0; i--) {
const name = targetAttrs[i].name;
if (isIgnored) {
if (name.startsWith("data-") && !source.hasAttribute(name) && !PHX_PENDING_ATTRS.includes(name)) {
target.removeAttribute(name);
}
} else {
if (!source.hasAttribute(name)) {
target.removeAttribute(name);
}
}
}
},
mergeFocusedInput(target, source) {
if (!(target instanceof HTMLSelectElement)) {
DOM.mergeAttrs(target, source, { exclude: ["value"] });
}
if (source.readOnly) {
target.setAttribute("readonly", true);
} else {
target.removeAttribute("readonly");
}
},
hasSelectionRange(el) {
return el.setSelectionRange && (el.type === "text" || el.type === "textarea");
},
restoreFocus(focused, selectionStart, selectionEnd) {
if (focused instanceof HTMLSelectElement) {
focused.focus();
}
if (!DOM.isTextualInput(focused)) {
return;
}
const wasFocused = focused.matches(":focus");
if (!wasFocused) {
focused.focus();
}
if (this.hasSelectionRange(focused)) {
focused.setSelectionRange(selectionStart, selectionEnd);
}
},
isFormInput(el) {
if (el.localName && customElements.get(el.localName)) {
return customElements.get(el.localName)[`formAssociated`];
}
return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== "button";
},
syncAttrsToProps(el) {
if (el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0) {
el.checked = el.getAttribute("checked") !== null;
}
},
isTextualInput(el) {
return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
},
isNowTriggerFormExternal(el, phxTriggerExternal) {
return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null && document.body.contains(el);
},
cleanChildNodes(container, phxUpdate) {
if (DOM.isPhxUpdate(container, phxUpdate, ["append", "prepend", PHX_STREAM])) {
const toRemove = [];
container.childNodes.forEach((childNode) => {
if (!childNode.id) {
const isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === "";
if (!isEmptyTextNode && childNode.nodeType !== Node.COMMENT_NODE) {
logError(
`only HTML element tags with an id are allowed inside containers with phx-update.
removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
`
);
}
toRemove.push(childNode);
}
});
toRemove.forEach((childNode) => childNode.remove());
}
},
replaceRootContainer(container, tagName, attrs) {
const retainedAttrs = /* @__PURE__ */ new Set([
"id",
PHX_SESSION,
PHX_STATIC,
PHX_MAIN,
PHX_ROOT_ID
]);
if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
return container;
} else {
const newContainer = document.createElement(tagName);
Object.keys(attrs).forEach(
(attr) => newContainer.setAttribute(attr, attrs[attr])
);
retainedAttrs.forEach(
(attr) => newContainer.setAttribute(attr, container.getAttribute(attr))
);
newContainer.innerHTML = container.innerHTML;
container.replaceWith(newContainer);
return newContainer;
}
},
getSticky(el, name, defaultVal) {
const op = (DOM.private(el, "sticky") || []).find(
([existingName]) => name === existingName
);
if (op) {
const [_name, _op, stashedResult] = op;
return stashedResult;
} else {
return typeof defaultVal === "function" ? defaultVal() : defaultVal;
}
},
deleteSticky(el, name) {
this.updatePrivate(el, "sticky", [], (ops) => {
return ops.filter(([existingName, _]) => existingName !== name);
});
},
putSticky(el, name, op) {
const stashedResult = op(el);
this.updatePrivate(el, "sticky", [], (ops) => {
const existingIndex = ops.findIndex(
([existingName]) => name === existingName
);
if (existingIndex >= 0) {
ops[existingIndex] = [name, op, stashedResult];
} else {
ops.push([name, op, stashedResult]);
}
return ops;
});
},
applyStickyOperations(el) {
const ops = DOM.private(el, "sticky");
if (!ops) {
return;
}
ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
},
isLocked(el) {
return el.hasAttribute && el.hasAttribute(PHX_REF_LOCK);
},
attributeIgnored(attribute, ignoredAttributes) {
return ignoredAttributes.some(
(toIgnore) => attribute.name == toIgnore || toIgnore === "*" || toIgnore.includes("*") && attribute.name.match(toIgnore) != null
);
}
};
var dom_default = DOM;
var UploadEntry = class {
static isActive(fileEl, file) {
const isNew = file._phxRef === void 0;
const activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
const isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
return file.size > 0 && (isNew || isActive);
}
static isPreflighted(fileEl, file) {
const preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(",");
const isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
return isPreflighted && this.isActive(fileEl, file);
}
static isPreflightInProgress(file) {
return file._preflightInProgress === true;
}
static markPreflightInProgress(file) {
file._preflightInProgress = true;
}
constructor(fileEl, file, view, autoUpload) {
this.ref = LiveUploader.genFileRef(file);
this.fileEl = fileEl;
this.file = file;
this.view = view;
this.meta = null;
this._isCancelled = false;
this._isDone = false;
this._progress = 0;
this._lastProgressSent = -1;
this._onDone = function() {
};
this._onElUpdated = this.onElUpdated.bind(this);
this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
this.autoUpload = autoUpload;
}
metadata() {
return this.meta;
}
progress(progress) {
this._progress = Math.floor(progress);
if (this._progress > this._lastProgressSent) {
if (this._progress >= 100) {
this._progress = 100;
this._lastProgressSent = 100;
this._isDone = true;
this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {
LiveUploader.untrackFile(this.fileEl, this.file);
this._onDone();
});
} else {
this._lastProgressSent = this._progress;
this.view.pushFileProgress(this.fileEl, this.ref, this._progress);
}
}
}
isCancelled() {
return this._isCancelled;
}
cancel() {
this.file._preflightInProgress = false;
this._isCancelled = true;
this._isDone = true;
this._onDone();
}
isDone() {
return this._isDone;
}
error(reason = "failed") {
this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
if (!this.isAutoUpload()) {
LiveUploader.clearFiles(this.fileEl);
}
}
isAutoUpload() {
return this.autoUpload;
}
//private
onDone(callback) {
this._onDone = () => {
this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
callback();
};
}
onElUpdated() {
const activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
if (activeRefs.indexOf(this.ref) === -1) {
LiveUploader.untrackFile(this.fileEl, this.file);
this.cancel();
}
}
toPreflightPayload() {
return {
last_modified: this.file.lastModified,
name: this.file.name,
relative_path: this.file.webkitRelativePath,
size: this.file.size,
type: this.file.type,
ref: this.ref,
meta: typeof this.file.meta === "function" ? this.file.meta() : void 0
};
}
uploader(uploaders) {
if (this.meta.uploader) {
const callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`);
return { name: this.meta.uploader, callback };
} else {
return { name: "channel", callback: channelUploader };
}
}
zipPostFlight(resp) {
this.meta = resp.entries[this.ref];
if (!this.meta) {
logError(`no preflight upload response returned with ref ${this.ref}`, {
input: this.fileEl,
response: resp
});
}
}
};
var liveUploaderFileRef = 0;
var LiveUploader = class _LiveUploader {
static genFileRef(file) {
const ref = file._phxRef;
if (ref !== void 0) {
return ref;
} else {
file._phxRef = (liveUploaderFileRef++).toString();
return file._phxRef;
}
}
static getEntryDataURL(inputEl, ref, callback) {
const file = this.activeFiles(inputEl).find(
(file2) => this.genFileRef(file2) === ref
);
callback(URL.createObjectURL(file));
}
static hasUploadsInProgress(formEl) {
let active = 0;
dom_default.findUploadInputs(formEl).forEach((input) => {
if (input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)) {
active++;
}
});
return active > 0;
}
static serializeUploads(inputEl) {
const files = this.activeFiles(inputEl);
const fileData = {};
files.forEach((file) => {
const entry = { path: inputEl.name };
const uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF);
fileData[uploadRef] = fileData[uploadRef] || [];
entry.ref = this.genFileRef(file);
entry.last_modified = file.lastModified;
entry.name = file.name || entry.ref;
entry.relative_path = file.webkitRelativePath;
entry.type = file.type;
entry.size = file.size;
if (typeof file.meta === "function") {
entry.meta = file.meta();
}
fileData[uploadRef].push(entry);
});
return fileData;
}
static clearFiles(inputEl) {
inputEl.value = null;
inputEl.removeAttribute(PHX_UPLOAD_REF);
dom_default.putPrivate(inputEl, "files", []);
}
static untrackFile(inputEl, file) {
dom_default.putPrivate(
inputEl,
"files",
dom_default.private(inputEl, "files").filter((f) => !Object.is(f, file))
);
}
/**
* @param {HTMLInputElement} inputEl
* @param {Array<File|Blob>} files
* @param {DataTransfer} [dataTransfer]
*/
static trackFiles(inputEl, files, dataTransfer) {
if (inputEl.getAttribute("multiple") !== null) {
const newFiles = files.filter(
(file) => !this.activeFiles(inputEl).find((f) => Object.is(f, file))
);
dom_default.updatePrivate(
inputEl,
"files",
[],
(existing) => existing.concat(newFiles)
);
inputEl.value = null;
} else {
if (dataTransfer && dataTransfer.files.length > 0) {
inputEl.files = dataTransfer.files;
}
dom_default.putPrivate(inputEl, "files", files);
}
}
static activeFileInputs(formEl) {
const fileInputs = dom_default.findUploadInputs(formEl);
return Array.from(fileInputs).filter(
(el) => el.files && this.activeFiles(el).length > 0
);
}
static activeFiles(input) {
return (dom_default.private(input, "files") || []).filter(
(f) => UploadEntry.isActive(input, f)
);
}
static inputsAwaitingPreflight(formEl) {
const fileInputs = dom_default.findUploadInputs(formEl);
return Array.from(fileInputs).filter(
(input) => this.filesAwaitingPreflight(input).length > 0
);
}
static filesAwaitingPreflight(input) {
return this.activeFiles(input).filter(
(f) => !UploadEntry.isPreflighted(input, f) && !UploadEntry.isPreflightInProgress(f)
);
}
static markPreflightInProgress(entries) {
entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
}
constructor(inputEl, view, onComplete) {
this.autoUpload = dom_default.isAutoUpload(inputEl);
this.view = view;
this.onComplete = onComplete;
this._entries = Array.from(
_LiveUploader.filesAwaitingPreflight(inputEl) || []
).map((file) => new UploadEntry(inputEl, file, view, this.autoUpload));
_LiveUploader.markPreflightInProgress(this._entries);
this.numEntriesInProgress = this._entries.length;
}
isAutoUpload() {
return this.autoUpload;
}
entries() {
return this._entries;
}
initAdapterUpload(resp, onError, liveSocket) {
this._entries = this._entries.map((entry) => {
if (entry.isCancelled()) {
this.numEntriesInProgress--;
if (this.numEntriesInProgress === 0) {
this.onComplete();
}
} else {
entry.zipPostFlight(resp);
entry.onDone(() => {
this.numEntriesInProgress--;
if (this.numEntriesInProgress === 0) {
this.onComplete();
}
});
}
return entry;
});
const groupedEntries = this._entries.reduce((acc, entry) => {
if (!entry.meta) {
return acc;
}
const { name, callback } = entry.uploader(liveSocket.uploaders);
acc[name] = acc[name] || { callback, entries: [] };
acc[name].entries.push(entry);
return acc;
}, {});
for (const name in groupedEntries) {
const { callback, entries } = groupedEntries[name];
callback(entries, onError, resp, liveSocket);
}
}
};
var ARIA = {
anyOf(instance, classes) {
return classes.find((name) => instance instanceof name);
},
isFocusable(el, interactiveOnly) {
return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [
HTMLInputElement,
HTMLSelectElement,
HTMLTextAreaElement,
HTMLButtonElement
]) || el instanceof HTMLIFrameElement || el.tabIndex >= 0 && el.getAttribute("aria-hidden") !== "true" || !interactiveOnly && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true";
},
attemptFocus(el, interactiveOnly) {
if (this.isFocusable(el, interactiveOnly)) {
try {
el.focus();
} catch {
}
}
return !!document.activeElement && document.activeElement.isSameNode(el);
},
focusFirstInteractive(el) {
let child = el.firstElementChild;
while (child) {
if (this.attemptFocus(child, true) || this.focusFirstInteractive(child)) {
return true;
}
child = child.nextElementSibling;
}
},
focusFirst(el) {
let child = el.firstElementChild;
while (child) {
if (this.attemptFocus(child) || this.focusFirst(child)) {
return true;
}
child = child.nextElementSibling;
}
},
focusLast(el) {
let child = el.lastElementChild;
while (child) {
if (this.attemptFocus(child) || this.focusLast(child)) {
return true;
}
child = child.previousElementSibling;
}
}
};
var aria_default = ARIA;
var Hooks = {
LiveFileUpload: {
activeRefs() {
return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS);
},
preflightedRefs() {
return this.el.getAttribute(PHX_PREFLIGHTED_REFS);
},
mounted() {
this.js().ignoreAttributes(this.el, ["value"]);
this.preflightedWas = this.preflightedRefs();
},
updated() {
const newPreflights = this.preflightedRefs();
if (this.preflightedWas !== newPreflights) {
this.preflightedWas = newPreflights;
if (newPreflights === "") {
this.__view().cancelSubmit(this.el.form);
}
}
if (this.activeRefs() === "") {
this.el.value = null;
}
this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED));
}
},
LiveImgPreview: {
mounted() {
this.ref = this.el.getAttribute("data-phx-entry-ref");
this.inputEl = document.getElementById(
this.el.getAttribute(PHX_UPLOAD_REF)
);
LiveUploader.getEntryDataURL(this.inputEl, this.ref, (url) => {
this.url = url;
this.el.src = url;
});
},
destroyed() {
URL.revokeObjectURL(this.url);
}
},
FocusWrap: {
mounted() {
this.focusStart = this.el.firstElementChild;
this.focusEnd = this.el.lastElementChild;
this.focusStart.addEventListener("focus", (e) => {
if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
const nextFocus = e.target.nextElementSibling;
aria_default.attemptFocus(nextFocus) || aria_default.focusFirst(nextFocus);
} else {
aria_default.focusLast(this.el);
}
});
this.focusEnd.addEventListener("focus", (e) => {
if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
const nextFocus = e.target.previousElementSibling;
aria_default.attemptFocus(nextFocus) || aria_default.focusLast(nextFocus);
} else {
aria_default.focusFirst(this.el);
}
});
if (!this.el.contains(document.activeElement)) {
this.el.addEventListener("phx:show-end", () => this.el.focus());
if (window.getComputedStyle(this.el).display !== "none") {
aria_default.focusFirst(this.el);
}
}
}
}
};
var findScrollContainer = (el) => {
if (["HTML", "BODY"].indexOf(el.nodeName.toUpperCase()) >= 0)
return null;
if (["scroll", "auto"].indexOf(getComputedStyle(el).overflowY) >= 0)
return el;
return findScrollContainer(el.parentElement);
};
var scrollTop = (scrollContainer) => {
if (scrollContainer) {
return scrollContainer.scrollTop;
} else {
return document.documentElement.scrollTop || document.body.scrollTop;
}
};
var bottom = (scrollContainer) => {
if (scrollContainer) {
return scrollContainer.getBoundingClientRect().bottom;
} else {
return window.innerHeight || document.documentElement.clientHeight;
}
};
var top = (scrollContainer) => {
if (scrollContainer) {
return scrollContainer.getBoundingClientRect().top;
} else {
return 0;
}
};
var isAtViewportTop = (el, scrollContainer) => {
const rect = el.getBoundingClientRect();
return Math.ceil(rect.top) >= top(scrollContainer) && Math.ceil(rect.left) >= 0 && Math.floor(rect.top) <= bottom(scrollContainer);
};
var isAtViewportBottom = (el, scrollContainer) => {
const rect = el.getBoundingClientRect();
return Math.ceil(rect.bottom) >= top(scrollContainer) && Math.ceil(rect.left) >= 0 && Math.floor(rect.bottom) <= bottom(scrollContainer);
};
var isWithinViewport = (el, scrollContainer) => {
const rect = el.getBoundingClientRect();
return Math.ceil(rect.top) >= top(scrollContainer) && Math.ceil(rect.left) >= 0 && Math.floor(rect.top) <= bottom(scrollContainer);
};
Hooks.InfiniteScroll = {
mounted() {
this.scrollContainer = findScrollContainer(this.el);
let scrollBefore = scrollTop(this.scrollContainer);
let topOverran = false;
const throttleInterval = 500;
let pendingOp = null;
const onTopOverrun = this.throttle(
throttleInterval,
(topEvent, firstChild) => {
pendingOp = () => true;
this.liveSocket.js().push(this.el, topEvent, {
value: { id: firstChild.id, _overran: true },
callback: () => {
pendingOp = null;
}
});
}
);
const onFirstChildAtTop = this.throttle(
throttleInterval,
(topEvent, firstChild) => {
pendingOp = () => firstChild.scrollIntoView({ block: "start" });
this.liveSocket.js().push(this.el, topEvent, {
value: { id: firstChild.id },
callback: () => {
pendingOp = null;
window.requestAnimationFrame(() => {
if (!isWithinViewport(firstChild, this.scrollContainer)) {
firstChild.scrollIntoView({ block: "start" });
}
});
}
});
}
);
const onLastChildAtBottom = this.throttle(
throttleInterval,
(bottomEvent, lastChild) => {
pendingOp = () => lastChild.scrollIntoView({ block: "end" });
this.liveSocket.js().push(this.el, bottomEvent, {
value: { id: lastChild.id },
callback: () => {
pendingOp = null;
window.requestAnimationFrame(() => {
if (!isWithinViewport(lastChild, this.scrollContainer)) {
lastChild.scrollIntoView({ block: "end" });
}
});
}
});
}
);
this.onScroll = (_e) => {
const scrollNow = scrollTop(this.scrollContainer);
if (pendingOp) {
scrollBefore = scrollNow;
return pendingOp();
}
const rect = this.findOverrunTarget();
const topEvent = this.el.getAttribute(
this.liveSocket.binding("viewport-top")
);
const bottomEvent = this.el.getAttribute(
this.liveSocket.binding("viewport-bottom")
);
const lastChild = this.el.lastElementChild;
const firstChild = this.el.firstElementChild;
const isScrollingUp = scrollNow < scrollBefore;
const isScrollingDown = scrollNow > scrollBefore;
if (isScrollingUp && topEvent && !topOverran && rect.top >= 0) {
topOverran = true;
onTopOverrun(topEvent, firstChild);
} else if (isScrollingDown && topOverran && rect.top <= 0) {
topOverran = false;
}
if (topEvent && isScrollingUp && isAtViewportTop(firstChild, this.scrollContainer)) {
onFirstChildAtTop(topEvent, firstChild);
} else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild, this.scrollContainer)) {
onLastChildAtBottom(bottomEvent, lastChild);
}
scrollBefore = scrollNow;
};
if (this.scrollContainer) {
this.scrollContainer.addEventListener("scroll", this.onScroll);
} else {
window.addEventListener("scroll", this.onScroll);
}
},
destroyed() {
if (this.scrollContainer) {
this.scrollContainer.removeEventListener("scroll", this.onScroll);
} else {
window.removeEventListener("scroll", this.onScroll);
}
},
throttle(interval, callback) {
let lastCallAt = 0;
let timer;
return (...args) => {
const now = Date.now();
const remainingTime = interval - (now - lastCallAt);
if (remainingTime <= 0 || remainingTime > interval) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastCallAt = now;
callback(...args);
} else if (!timer) {
timer = setTimeout(() => {
lastCallAt = Date.now();
timer = null;
callback(...args);
}, remainingTime);
}
};
},
findOverrunTarget() {
let rect;
const overrunTarget = this.el.getAttribute(
this.liveSocket.binding(PHX_VIEWPORT_OVERRUN_TARGET)
);
if (overrunTarget) {
const overrunEl = document.getElementById(overrunTarget);
if (overrunEl) {
rect = overrunEl.getBoundingClientRect();
} else {
throw new Error("did not find element with id " + overrunTarget);
}
} else {
rect = this.el.getBoundingClientRect();
}
return rect;
}
};
var hooks_default = Hooks;
var ElementRef = class {
static onUnlock(el, callback) {
if (!dom_default.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) {
this.el = el;
this.loadingRef = el.hasAttribute(PHX_REF_LOADING) ? parseInt(el.getAttribute(PHX_REF_LOADING), 10) : null;
this.lockRef = el.hasAttribute(PHX_REF_LOCK) ? parseInt(el.getAttribute(PHX_REF_LOCK), 10) : null;
}
// public
maybeUndo(ref, phxEvent, eachCloneCallback) {
if (!this.isWithin(ref)) {
dom_default.updatePrivate(this.el, PHX_PENDING_REFS, [], (pendingRefs) => {
pendingRefs.push(ref);
return pendingRefs;
});
return;
}
this.undoLocks(ref, phxEvent, eachCloneCallback);
this.undoLoading(ref, phxEvent);
dom_default.updatePrivate(this.el, PHX_PENDING_REFS, [], (pendingRefs) => {
return pendingRefs.filter((pendingRef) => {
let opts = {
detail: { ref: pendingRef, event: phxEvent },
bubbles: true,
cancelable: false
};
if (this.loadingRef && this.loadingRef > pendingRef) {
this.el.dispatchEvent(
new CustomEvent(`phx:undo-loading:${pendingRef}`, opts)
);
}
if (this.lockRef && this.lockRef > pendingRef) {
this.el.dispatchEvent(
new CustomEvent(`phx:undo-lock:${pendingRef}`, opts)
);
}
return pendingRef > ref;
});
});
if (this.isFullyResolvedBy(ref)) {
this.el.removeAttribute(PHX_REF_SRC);
}
}
// private
isWithin(ref) {
return !(this.loadingRef !== null && this.loadingRef > ref && this.lockRef !== null && this.lockRef > ref);
}
// Check for cloned PHX_REF_LOCK element that has been morphed behind
// the scenes while this element was locked in the DOM.
// When we apply the cloned tree to the active DOM element, we must
//
// 1. execute pending mounted hooks for nodes now in the DOM
// 2. undo any ref inside the cloned tree that has since been ack'd
undoLocks(ref, phxEvent, eachCloneCallback) {
if (!this.isLockUndoneBy(ref)) {
return;
}
const clonedTree = dom_default.private(this.el, PHX_REF_LOCK);
if (clonedTree) {
eachCloneCallback(clonedTree);
dom_default.deletePrivate(this.el, PHX_REF_LOCK);
}
this.el.removeAttribute(PHX_REF_LOCK);
const opts = {
detail: { ref, event: phxEvent },
bubbles: true,
cancelable: false
};
this.el.dispatchEvent(
new CustomEvent(`phx:undo-lock:${this.lockRef}`, opts)
);
}
undoLoading(ref, phxEvent) {
if (!this.isLoadingUndoneBy(ref)) {
if (this.canUndoLoading(ref) && this.el.classList.contains("phx-submit-loading")) {
this.el.classList.remove("phx-change-loading");
}
return;
}
if (this.canUndoLoading(ref)) {
this.el.removeAttribute(PHX_REF_LOADING);
const disabledVal = this.el.getAttribute(PHX_DISABLED);
const readOnlyVal = this.el.getAttribute(PHX_READONLY);
if (readOnlyVal !== null) {
this.el.readOnly = readOnlyVal === "true" ? true : false;
this.el.removeAttribute(PHX_READONLY);
}
if (disabledVal !== null) {
this.el.disabled = disabledVal === "true" ? true : false;
this.el.removeAttribute(PHX_DISABLED);
}
const disableRestore = this.el.getAttribute(PHX_DISABLE_WITH_RESTORE);
if (disableRestore !== null) {
this.el.textContent = disableRestore;
this.el.removeAttribute(PHX_DISABLE_WITH_RESTORE);
}
const opts = {
detail: { ref, event: phxEvent },
bubbles: true,
cancelable: false
};
this.el.dispatchEvent(
new CustomEvent(`phx:undo-loading:${this.loadingRef}`, opts)
);
}
PHX_EVENT_CLASSES.forEach((name) => {
if (name !== "phx-submit-loading" || this.canUndoLoading(ref)) {
dom_default.removeClass(this.el, name);
}
});
}
isLoadingUndoneBy(ref) {
return this.loadingRef === null ? false : this.loadingRef <= ref;
}
isLockUndoneBy(ref) {
return this.lockRef === null ? false : this.lockRef <= ref;
}
isFullyResolvedBy(ref) {
return (this.loadingRef === null || this.loadingRef <= ref) && (this.lockRef === null || this.lockRef <= ref);
}
// only remove the phx-submit-loading class if we are not locked
canUndoLoading(ref) {
return this.lockRef === null || this.lockRef <= ref;
}
};
var DOMPostMorphRestorer = class {
constructor(containerBefore, containerAfter, updateType) {
const idsBefore = /* @__PURE__ */ new Set();
const idsAfter = new Set(
[...containerAfter.children].map((child) => child.id)
);
const elementsToModify = [];
Array.from(containerBefore.children).forEach((child) => {
if (child.id) {
idsBefore.add(child.id);
if (idsAfter.has(child.id)) {
const previousElementId = child.previousElementSibling && child.previousElementSibling.id;
elementsToModify.push({
elementId: child.id,
previousElementId
});
}
}
});
this.containerId = containerAfter.id;
this.updateType = updateType;
this.elementsToModify = elementsToModify;
this.elementIdsToAdd = [...idsAfter].filter((id) => !idsBefore.has(id));
}
// We do the following to optimize append/prepend operations:
// 1) Track ids of modified elements & of new elements
// 2) All the modified elements are put back in the correct position in the DOM tree
// by storing the id of their previous sibling
// 3) New elements are going to be put in the right place by morphdom during append.
// For prepend, we move them to the first position in the container
perform() {
const container = dom_default.byId(this.containerId);
if (!container) {
return;
}
this.elementsToModify.forEach((elementToModify) => {
if (elementToModify.previousElementId) {
maybe(
document.getElementById(elementToModify.previousElementId),
(previousElem) => {
maybe(
document.getElementById(elementToModify.elementId),
(elem) => {
const isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id;
if (!isInRightPlace) {
previousElem.insertAdjacentElement("afterend", elem);
}
}
);
}
);
} else {
maybe(document.getElementById(elementToModify.elementId), (elem) => {
const isInRightPlace = elem.previousElementSibling == null;
if (!isInRightPlace) {
container.insertAdjacentElement("afterbegin", elem);
}
});
}
});
if (this.updateType == "prepend") {
this.elementIdsToAdd.reverse().forEach((elemId) => {
maybe(
document.getElementById(elemId),
(elem) => container.insertAdjacentElement("afterbegin", elem)
);
});
}
}
};
var DOCUMENT_FRAGMENT_NODE = 11;
function morphAttrs(fromNode, toNode) {
var toNodeAttrs = toNode.attributes;
var attr;
var attrName;
var attrNamespaceURI;
var attrValue;
var fromValue;
if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
return;
}
for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
attr = toNodeAttrs[i];
attrName = attr.name;
attrNamespaceURI = attr.namespaceURI;
attrValue = attr.value;
if (attrNamespaceURI) {
attrName = attr.localName || attrName;
fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
if (fromValue !== attrValue) {
if (attr.prefix === "xmlns") {
attrName = attr.name;
}
fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
}
} else {
fromValue = fromNode.getAttribute(attrName);
if (fromValue !== attrValue) {
fromNode.setAttribute(attrName, attrValue);
}
}
}
var fromNodeAttrs = fromNode.attributes;
for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
attr = fromNodeAttrs[d];
attrName = attr.name;
attrNamespaceURI = attr.namespaceURI;
if (attrNamespaceURI) {
attrName = attr.localName || attrName;
if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
fromNode.removeAttributeNS(attrNamespaceURI, attrName);
}
} else {
if (!toNode.hasAttribute(attrName)) {
fromNode.removeAttribute(attrName);
}
}
}
}
var range;
var NS_XHTML = "http://www.w3.org/1999/xhtml";
var doc = typeof document === "undefined" ? void 0 : document;
var HAS_TEMPLATE_SUPPORT = !!doc && "content" in doc.createElement("template");
var HAS_RANGE_SUPPORT = !!doc && doc.createRange && "createContextualFragment" in doc.createRange();
function createFragmentFromTemplate(str) {
var template = doc.createElement("template");
template.innerHTML = str;
return template.content.childNodes[0];
}
function createFragmentFromRange(str) {
if (!range) {
range = doc.createRange();
range.selectNode(doc.body);
}
var fragment = range.createContextualFragment(str);
return fragment.childNodes[0];
}
function createFragmentFromWrap(str) {
var fragment = doc.createElement("body");
fragment.innerHTML = str;
return fragment.childNodes[0];
}
function toElement(str) {
str = str.trim();
if (HAS_TEMPLATE_SUPPORT) {
return createFragmentFromTemplate(str);
} else if (HAS_RANGE_SUPPORT) {
return createFragmentFromRange(str);
}
return createFragmentFromWrap(str);
}
function compareNodeNames(fromEl, toEl) {
var fromNodeName = fromEl.nodeName;
var toNodeName = toEl.nodeName;
var fromCodeStart, toCodeStart;
if (fromNodeName === toNodeName) {
return true;
}
fromCodeStart = fromNodeName.charCodeAt(0);
toCodeStart = toNodeName.charCodeAt(0);
if (fromCodeStart <= 90 && toCodeStart >= 97) {
return fromNodeName === toNodeName.toUpperCase();
} else if (toCodeStart <= 90 && fromCodeStart >= 97) {
return toNodeName === fromNodeName.toUpperCase();
} else {
return false;
}
}
function createElementNS(name, namespaceURI) {
return !namespaceURI || namespaceURI === NS_XHTML ? doc.createElement(name) : doc.createElementNS(namespaceURI, name);
}
function moveChildren(fromEl, toEl) {
var curChild = fromEl.firstChild;
while (curChild) {
var nextChild = curChild.nextSibling;
toEl.appendChild(curChild);
curChild = nextChild;
}
return toEl;
}
function syncBooleanAttrProp(fromEl, toEl, name) {
if (fromEl[name] !== toEl[name]) {
fromEl[name] = toEl[name];
if (fromEl[name]) {
fromEl.setAttribute(name, "");
} else {
fromEl.removeAttribute(name);
}
}
}
var specialElHandlers = {
OPTION: function(fromEl, toEl) {
var parentNode = fromEl.parentNode;
if (parentNode) {
var parentName = parentNode.nodeName.toUpperCase();
if (parentName === "OPTGROUP") {
parentNode = parentNode.parentNode;
parentName = parentNode && parentNode.nodeName.toUpperCase();
}
if (parentName === "SELECT" && !parentNode.hasAttribute("multiple")) {
if (fromEl.hasAttribute("selected") && !toEl.selected) {
fromEl.setAttribute("selected", "selected");
fromEl.removeAttribute("selected");
}
parentNode.selectedIndex = -1;
}
}
syncBooleanAttrProp(fromEl, toEl, "selected");
},
/**
* The "value" attribute is special for the <input> element since it sets
* the initial value. Changing the "value" attribute without changing the
* "value" property will have no effect since it is only used to the set the
* initial value. Similar for the "checked" attribute, and "disabled".
*/
INPUT: function(fromEl, toEl) {
syncBooleanAttrProp(fromEl, toEl, "checked");
syncBooleanAttrProp(fromEl, toEl, "disabled");
if (fromEl.value !== toEl.value) {
fromEl.value = toEl.value;
}
if (!toEl.hasAttribute("value")) {
fromEl.removeAttribute("value");
}
},
TEXTAREA: function(fromEl, toEl) {
var newValue = toEl.value;
if (fromEl.value !== newValue) {
fromEl.value = newValue;
}
var firstChild = fromEl.firstChild;
if (firstChild) {
var oldValue = firstChild.nodeValue;
if (oldValue == newValue || !newValue && oldValue == fromEl.placeholder) {
return;
}
firstChild.nodeValue = newValue;
}
},
SELECT: function(fromEl, toEl) {
if (!toEl.hasAttribute("multiple")) {
var selectedIndex = -1;
var i = 0;
var curChild = fromEl.firstChild;
var optgroup;
var nodeName;
while (curChild) {
nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
if (nodeName === "OPTGROUP") {
optgroup = curChild;
curChild = optgroup.firstChild;
if (!curChild) {
curChild = optgroup.nextSibling;
optgroup = null;
}
} else {
if (nodeName === "OPTION") {
if (curChild.hasAttribute("selected")) {
selectedIndex = i;
break;
}
i++;
}
curChild = curChild.nextSibling;
if (!curChild && optgroup) {
curChild = optgroup.nextSibling;
optgroup = null;
}
}
}
fromEl.selectedIndex = selectedIndex;
}
}
};
var ELEMENT_NODE = 1;
var DOCUMENT_FRAGMENT_NODE$1 = 11;
var TEXT_NODE = 3;
var COMMENT_NODE = 8;
function noop() {
}
function defaultGetNodeKey(node) {
if (node) {
return node.getAttribute && node.getAttribute("id") || node.id;
}
}
function morphdomFactory(morphAttrs2) {
return function morphdom2(fromNode, toNode, options) {
if (!options) {
options = {};
}
if (typeof toNode === "string") {
if (fromNode.nodeName === "#document" || fromNode.nodeName === "HTML") {
var toNodeHtml = toNode;
toNode = doc.createElement("html");
toNode.innerHTML = toNodeHtml;
} else if (fromNode.nodeName === "BODY") {
var toNodeBody = toNode;
toNode = doc.createElement("html");
toNode.innerHTML = toNodeBody;
var bodyElement = toNode.querySelector("body");
if (bodyElement) {
toNode = bodyElement;
}
} else {
toNode = toElement(toNode);
}
} else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
toNode = toNode.firstElementChild;
}
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
var onNodeAdded = options.onNodeAdded || noop;
var onBeforeElUpdated = options.onBeforeElUpdated || noop;
var onElUpdated = options.onElUpdated || noop;
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
var onNodeDiscarded = options.onNodeDiscarded || noop;
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
var skipFromChildren = options.skipFromChildren || noop;
var addChild = options.addChild || function(parent, child) {
return parent.appendChild(child);
};
var childrenOnly = options.childrenOnly === true;
var fromNodesLookup = /* @__PURE__ */ Object.create(null);
var keyedRemovalList = [];
function addKeyedRemoval(key) {
keyedRemovalList.push(key);
}
function walkDiscardedChildNodes(node, skipKeyedNodes) {
if (node.nodeType === ELEMENT_NODE) {
var curChild = node.firstChild;
while (curChild) {
var key = void 0;
if (skipKeyedNodes && (key = getNodeKey(curChild))) {
addKeyedRemoval(key);
} else {
onNodeDiscarded(curChild);
if (curChild.firstChild) {
walkDiscardedChildNodes(curChild, skipKeyedNodes);
}
}
curChild = curChild.nextSibling;
}
}
}
function removeNode(node, parentNode, skipKeyedNodes) {
if (onBeforeNodeDiscarded(node) === false) {
return;
}
if (parentNode) {
parentNode.removeChild(node);
}
onNodeDiscarded(node);
walkDiscardedChildNodes(node, skipKeyedNodes);
}
function indexTree(node) {
if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
var curChild = node.firstChild;
while (curChild) {
var key = getNodeKey(curChild);
if (key) {
fromNodesLookup[key] = curChild;
}
indexTree(curChild);
curChild = curChild.nextSibling;
}
}
}
indexTree(fromNode);
function handleNodeAdded(el) {
onNodeAdded(el);
var curChild = el.firstChild;
while (curChild) {
var nextSibling = curChild.nextSibling;
var key = getNodeKey(curChild);
if (key) {
var unmatchedFromEl = fromNodesLookup[key];
if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
morphEl(unmatchedFromEl, curChild);
} else {
handleNodeAdded(curChild);
}
} else {
handleNodeAdded(curChild);
}
curChild = nextSibling;
}
}
function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
while (curFromNodeChild) {
var fromNextSibling = curFromNodeChild.nextSibling;
if (curFromNodeKey = getNodeKey(curFromNodeChild)) {
addKeyedRemoval(curFromNodeKey);
} else {
removeNode(
curFromNodeChild,
fromEl,
true
/* skip keyed nodes */
);
}
curFromNodeChild = fromNextSibling;
}
}
function morphEl(fromEl, toEl, childrenOnly2) {
var toElKey = getNodeKey(toEl);
if (toElKey) {
delete fromNodesLookup[toElKey];
}
if (!childrenOnly2) {
var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);
if (beforeUpdateResult === false) {
return;
} else if (beforeUpdateResult instanceof HTMLElement) {
fromEl = beforeUpdateResult;
indexTree(fromEl);
}
morphAttrs2(fromEl, toEl);
onElUpdated(fromEl);
if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
return;
}
}
if (fromEl.nodeName !== "TEXTAREA") {
morphChildren(fromEl, toEl);
} else {
specialElHandlers.TEXTAREA(fromEl, toEl);
}
}
function morphChildren(fromEl, toEl) {
var skipFrom = skipFromChildren(fromEl, toEl);
var curToNodeChild = toEl.firstChild;
var curFromNodeChild = fromEl.firstChild;
var curToNodeKey;
var curFromNodeKey;
var fromNextSibling;
var toNextSibling;
var matchingFromEl;
outer:
while (curToNodeChild) {
toNextSibling = curToNodeChild.nextSibling;
curToNodeKey = getNodeKey(curToNodeChild);
while (!skipFrom && curFromNodeChild) {
fromNextSibling = curFromNodeChild.nextSibling;
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
curToNodeChild = toNextSibling;
curFromNodeChild = fromNextSibling;
continue outer;
}
curFromNodeKey = getNodeKey(curFromNodeChild);
var curFromNodeType = curFromNodeChild.nodeType;
var isCompatible = void 0;
if (curFromNodeType === curToNodeChild.nodeType) {
if (curFromNodeType === ELEMENT_NODE) {
if (curToNodeKey) {
if (curToNodeKey !== curFromNodeKey) {
if (matchingFromEl = fromNodesLookup[curToNodeKey]) {
if (fromNextSibling === matchingFromEl) {
isCompatible = false;
} else {
fromEl.insertBefore(matchingFromEl, curFromNodeChild);
if (curFromNodeKey) {
addKeyedRemoval(curFromNodeKey);
} else {
removeNode(
curFromNodeChild,
fromEl,
true
/* skip keyed nodes */
);
}
curFromNodeChild = matchingFromEl;
curFromNodeKey = getNodeKey(curFromNodeChild);
}
} else {
isCompatible = false;
}
}
} else if (curFromNodeKey) {
isCompatible = false;
}
isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
if (isCompatible) {
morphEl(curFromNodeChild, curToNodeChild);
}
} else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
isCompatible = true;
if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
}
}
}
if (isCompatible) {
curToNodeChild = toNextSibling;
curFromNodeChild = fromNextSibling;
continue outer;
}
if (curFromNodeKey) {
addKeyedRemoval(curFromNodeKey);
} else {
removeNode(
curFromNodeChild,
fromEl,
true
/* skip keyed nodes */
);
}
curFromNodeChild = fromNextSibling;
}
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
if (!skipFrom) {
addChild(fromEl, matchingFromEl);
}
morphEl(matchingFromEl, curToNodeChild);
} else {
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
if (onBeforeNodeAddedResult !== false) {
if (onBeforeNodeAddedResult) {
curToNodeChild = onBeforeNodeAddedResult;
}
if (curToNodeChild.actualize) {
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
}
addChild(fromEl, curToNodeChild);
handleNodeAdded(curToNodeChild);
}
}
curToNodeChild = toNextSibling;
curFromNodeChild = fromNextSibling;
}
cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
var specialElHandler = specialElHandlers[fromEl.nodeName];
if (specialElHandler) {
specialElHandler(fromEl, toEl);
}
}
var morphedNode = fromNode;
var morphedNodeType = morphedNode.nodeType;
var toNodeType = toNode.nodeType;
if (!childrenOnly) {
if (morphedNodeType === ELEMENT_NODE) {
if (toNodeType === ELEMENT_NODE) {
if (!compareNodeNames(fromNode, toNode)) {
onNodeDiscarded(fromNode);
morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
}
} else {
morphedNode = toNode;
}
} else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) {
if (toNodeType === morphedNodeType) {
if (morphedNode.nodeValue !== toNode.nodeValue) {
morphedNode.nodeValue = toNode.nodeValue;
}
return morphedNode;
} else {
morphedNode = toNode;
}
}
}
if (morphedNode === toNode) {
onNodeDiscarded(fromNode);
} else {
if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
return;
}
morphEl(morphedNode, toNode, childrenOnly);
if (keyedRemovalList) {
for (var i = 0, len = keyedRemovalList.length; i < len; i++) {
var elToRemove = fromNodesLookup[keyedRemovalList[i]];
if (elToRemove) {
removeNode(elToRemove, elToRemove.parentNode, false);
}
}
}
}
if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
if (morphedNode.actualize) {
morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
}
fromNode.parentNode.replaceChild(morphedNode, fromNode);
}
return morphedNode;
};
}
var morphdom = morphdomFactory(morphAttrs);
var morphdom_esm_default = morphdom;
var DOMPatch = class {
constructor(view, container, id, html, streams, targetCID, opts = {}) {
this.view = view;
this.liveSocket = view.liveSocket;
this.container = container;
this.id = id;
this.rootID = view.root.id;
this.html = html;
this.streams = streams;
this.streamInserts = {};
this.streamComponentRestore = {};
this.targetCID = targetCID;
this.cidPatch = isCid(this.targetCID);
this.pendingRemoves = [];
this.phxRemove = this.liveSocket.binding("remove");
this.targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container;
this.callbacks = {
beforeadded: [],
beforeupdated: [],
beforephxChildAdded: [],
afteradded: [],
afterupdated: [],
afterdiscarded: [],
afterphxChildAdded: [],
aftertransitionsDiscarded: []
};
this.withChildren = opts.withChildren || opts.undoRef || false;
this.undoRef = opts.undoRef;
}
before(kind, callback) {
this.callbacks[`before${kind}`].push(callback);
}
after(kind, callback) {
this.callbacks[`after${kind}`].push(callback);
}
trackBefore(kind, ...args) {
this.callbacks[`before${kind}`].forEach((callback) => callback(...args));
}
trackAfter(kind, ...args) {
this.callbacks[`after${kind}`].forEach((callback) => callback(...args));
}
markPrunableContentForRemoval() {
const phxUpdate = this.liveSocket.binding(PHX_UPDATE);
dom_default.all(
this.container,
`[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`,
(el) => {
el.setAttribute(PHX_PRUNE, "");
}
);
}
perform(isJoinPatch) {
const { view, liveSocket, html, container } = this;
let targetContainer = this.targetContainer;
if (this.isCIDPatch() && !this.targetContainer) {
return;
}
if (this.isCIDPatch()) {
const closestLock = targetContainer.closest(`[${PHX_REF_LOCK}]`);
if (closestLock && !closestLock.isSameNode(targetContainer)) {
const clonedTree = dom_default.private(closestLock, PHX_REF_LOCK);
if (clonedTree) {
targetContainer = clonedTree.querySelector(
`[data-phx-component="${this.targetCID}"]`
);
}
}
}
const focused = liveSocket.getActiveElement();
const { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
const phxUpdate = liveSocket.binding(PHX_UPDATE);
const phxViewportTop = liveSocket.binding(PHX_VIEWPORT_TOP);
const phxViewportBottom = liveSocket.binding(PHX_VIEWPORT_BOTTOM);
const phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
const added = [];
const updates = [];
const appendPrependUpdates = [];
let portalCallbacks = [];
let externalFormTriggered = null;
const morph = (targetContainer2, source, withChildren = this.withChildren) => {
const morphCallbacks = {
// normally, we are running with childrenOnly, as the patch HTML for a LV
// does not include the LV attrs (data-phx-session, etc.)
// when we are patching a live component, we do want to patch the root element as well;
// another case is the recursive patch of a stream item that was kept on reset (-> onBeforeNodeAdded)
childrenOnly: targetContainer2.getAttribute(PHX_COMPONENT) === null && !withChildren,
getNodeKey: (node) => {
if (dom_default.isPhxDestroyed(node)) {
return null;
}
if (isJoinPatch) {
return node.id;
}
return node.id || node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
},
// skip indexing from children when container is stream
skipFromChildren: (from) => {
return from.getAttribute(phxUpdate) === PHX_STREAM;
},
// tell morphdom how to add a child
addChild: (parent, child) => {
const { ref, streamAt } = this.getStreamInsert(child);
if (ref === void 0) {
return parent.appendChild(child);
}
this.setStreamRef(child, ref);
if (streamAt === 0) {
parent.insertAdjacentElement("afterbegin", child);
} else if (streamAt === -1) {
const lastChild = parent.lastElementChild;
if (lastChild && !lastChild.hasAttribute(PHX_STREAM_REF)) {
const nonStreamChild = Array.from(parent.children).find(
(c) => !c.hasAttribute(PHX_STREAM_REF)
);
parent.insertBefore(child, nonStreamChild);
} else {
parent.appendChild(child);
}
} else if (streamAt > 0) {
const sibling = Array.from(parent.children)[streamAt];
parent.insertBefore(child, sibling);
}
},
onBeforeNodeAdded: (el) => {
if (this.getStreamInsert(el)?.updateOnly && !this.streamComponentRestore[el.id]) {
return false;
}
dom_default.maintainPrivateHooks(el, el, phxViewportTop, phxViewportBottom);
this.trackBefore("added", el);
let morphedEl = el;
if (this.streamComponentRestore[el.id]) {
morphedEl = this.streamComponentRestore[el.id];
delete this.streamComponentRestore[el.id];
morph(morphedEl, el, true);
}
return morphedEl;
},
onNodeAdded: (el) => {
if (el.getAttribute) {
this.maybeReOrderStream(el, true);
}
if (dom_default.isPortalTemplate(el)) {
portalCallbacks.push(() => this.teleport(el, morph));
}
if (el instanceof HTMLImageElement && el.srcset) {
el.srcset = el.srcset;
} else if (el instanceof HTMLVideoElement && el.autoplay) {
el.play();
}
if (dom_default.isNowTriggerFormExternal(el, phxTriggerExternal)) {
externalFormTriggered = el;
}
if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
this.trackAfter("phxChildAdded", el);
}
if (el.nodeName === "SCRIPT" && el.hasAttribute(PHX_RUNTIME_HOOK)) {
this.handleRuntimeHook(el, source);
}
added.push(el);
},
onNodeDiscarded: (el) => this.onNodeDiscarded(el),
onBeforeNodeDiscarded: (el) => {
if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
return true;
}
if (el.parentElement !== null && el.id && dom_default.isPhxUpdate(el.parentElement, phxUpdate, [
PHX_STREAM,
"append",
"prepend"
])) {
return false;
}
if (el.getAttribute && el.getAttribute(PHX_TELEPORTED_REF)) {
return false;
}
if (this.maybePendingRemove(el)) {
return false;
}
if (this.skipCIDSibling(el)) {
return false;
}
if (dom_default.isPortalTemplate(el)) {
const teleportedEl = document.getElementById(
el.content.firstElementChild.id
);
if (teleportedEl) {
teleportedEl.remove();
morphCallbacks.onNodeDiscarded(teleportedEl);
this.view.dropPortalElementId(teleportedEl.id);
}
}
return true;
},
onElUpdated: (el) => {
if (dom_default.isNowTriggerFormExternal(el, phxTriggerExternal)) {
externalFormTriggered = el;
}
updates.push(el);
this.maybeReOrderStream(el, false);
},
onBeforeElUpdated: (fromEl, toEl) => {
if (fromEl.id && fromEl.isSameNode(targetContainer2) && fromEl.id !== toEl.id) {
morphCallbacks.onNodeDiscarded(fromEl);
fromEl.replaceWith(toEl);
return morphCallbacks.onNodeAdded(toEl);
}
dom_default.syncPendingAttrs(fromEl, toEl);
dom_default.maintainPrivateHooks(
fromEl,
toEl,
phxViewportTop,
phxViewportBottom
);
dom_default.cleanChildNodes(toEl, phxUpdate);
if (this.skipCIDSibling(toEl)) {
this.maybeReOrderStream(fromEl);
return false;
}
if (dom_default.isPhxSticky(fromEl)) {
[PHX_SESSION, PHX_STATIC, PHX_ROOT_ID].map((attr) => [
attr,
fromEl.getAttribute(attr),
toEl.getAttribute(attr)
]).forEach(([attr, fromVal, toVal]) => {
if (toVal && fromVal !== toVal) {
fromEl.setAttribute(attr, toVal);
}
});
return false;
}
if (dom_default.isIgnored(fromEl, phxUpdate) || fromEl.form && fromEl.form.isSameNode(externalFormTriggered)) {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeAttrs(fromEl, toEl, {
isIgnored: dom_default.isIgnored(fromEl, phxUpdate)
});
updates.push(fromEl);
dom_default.applyStickyOperations(fromEl);
return false;
}
if (fromEl.type === "number" && fromEl.validity && fromEl.validity.badInput) {
return false;
}
const isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
const focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
if (fromEl.hasAttribute(PHX_REF_SRC)) {
const ref = new ElementRef(fromEl);
if (ref.lockRef && (!this.undoRef || !ref.isLockUndoneBy(this.undoRef))) {
dom_default.applyStickyOperations(fromEl);
const isLocked = fromEl.hasAttribute(PHX_REF_LOCK);
const clone2 = isLocked ? dom_default.private(fromEl, PHX_REF_LOCK) || fromEl.cloneNode(true) : null;
if (clone2) {
dom_default.putPrivate(fromEl, PHX_REF_LOCK, clone2);
if (!isFocusedFormEl) {
fromEl = clone2;
}
}
}
}
if (dom_default.isPhxChild(toEl)) {
const prevSession = fromEl.getAttribute(PHX_SESSION);
dom_default.mergeAttrs(fromEl, toEl, { exclude: [PHX_STATIC] });
if (prevSession !== "") {
fromEl.setAttribute(PHX_SESSION, prevSession);
}
fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
dom_default.applyStickyOperations(fromEl);
return false;
}
if (this.undoRef && dom_default.private(toEl, PHX_REF_LOCK)) {
dom_default.putPrivate(
fromEl,
PHX_REF_LOCK,
dom_default.private(toEl, PHX_REF_LOCK)
);
}
dom_default.copyPrivates(toEl, fromEl);
if (dom_default.isPortalTemplate(toEl)) {
portalCallbacks.push(() => this.teleport(toEl, morph));
fromEl.content.replaceChildren(toEl.content.cloneNode(true));
return false;
}
if (isFocusedFormEl && fromEl.type !== "hidden" && !focusedSelectChanged) {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeFocusedInput(fromEl, toEl);
dom_default.syncAttrsToProps(fromEl);
updates.push(fromEl);
dom_default.applyStickyOperations(fromEl);
return false;
} else {
if (focusedSelectChanged) {
fromEl.blur();
}
if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
appendPrependUpdates.push(
new DOMPostMorphRestorer(
fromEl,
toEl,
toEl.getAttribute(phxUpdate)
)
);
}
dom_default.syncAttrsToProps(toEl);
dom_default.applyStickyOperations(toEl);
this.trackBefore("updated", fromEl, toEl);
return fromEl;
}
}
};
morphdom_esm_default(targetContainer2, source, morphCallbacks);
};
this.trackBefore("added", container);
this.trackBefore("updated", container, container);
liveSocket.time("morphdom", () => {
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
inserts.forEach(([key, streamAt, limit, updateOnly]) => {
this.streamInserts[key] = { ref, streamAt, limit, reset, updateOnly };
});
if (reset !== void 0) {
dom_default.all(document, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
this.removeStreamChildElement(child);
});
}
deleteIds.forEach((id) => {
const child = document.getElementById(id);
if (child) {
this.removeStreamChildElement(child);
}
});
});
if (isJoinPatch) {
dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`).filter((el) => this.view.ownsElement(el)).forEach((el) => {
Array.from(el.children).forEach((child) => {
this.removeStreamChildElement(child, true);
});
});
}
morph(targetContainer, html);
let teleportCount = 0;
while (portalCallbacks.length > 0 && teleportCount < 5) {
const copy = portalCallbacks.slice();
portalCallbacks = [];
copy.forEach((callback) => callback());
teleportCount++;
}
this.view.portalElementIds.forEach((id) => {
const el = document.getElementById(id);
if (el) {
const source = document.getElementById(
el.getAttribute(PHX_TELEPORTED_SRC)
);
if (!source) {
el.remove();
this.onNodeDiscarded(el);
this.view.dropPortalElementId(id);
}
}
});
});
if (liveSocket.isDebugEnabled()) {
detectDuplicateIds();
detectInvalidStreamInserts(this.streamInserts);
Array.from(document.querySelectorAll("input[name=id]")).forEach(
(node) => {
if (node instanceof HTMLInputElement && node.form) {
console.error(
'Detected an input with name="id" inside a form! This will cause problems when patching the DOM.\n',
node
);
}
}
);
}
if (appendPrependUpdates.length > 0) {
liveSocket.time("post-morph append/prepend restoration", () => {
appendPrependUpdates.forEach((update) => update.perform());
});
}
liveSocket.silenceEvents(
() => dom_default.restoreFocus(focused, selectionStart, selectionEnd)
);
dom_default.dispatchEvent(document, "phx:update");
added.forEach((el) => this.trackAfter("added", el));
updates.forEach((el) => this.trackAfter("updated", el));
this.transitionPendingRemoves();
if (externalFormTriggered) {
liveSocket.unload();
const submitter = dom_default.private(externalFormTriggered, "submitter");
if (submitter && submitter.name && targetContainer.contains(submitter)) {
const input = document.createElement("input");
input.type = "hidden";
const formId = submitter.getAttribute("form");
if (formId) {
input.setAttribute("form", formId);
}
input.name = submitter.name;
input.value = submitter.value;
submitter.parentElement.insertBefore(input, submitter);
}
Object.getPrototypeOf(externalFormTriggered).submit.call(
externalFormTriggered
);
}
return true;
}
onNodeDiscarded(el) {
if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
this.liveSocket.destroyViewByEl(el);
}
this.trackAfter("discarded", el);
}
maybePendingRemove(node) {
if (node.getAttribute && node.getAttribute(this.phxRemove) !== null) {
this.pendingRemoves.push(node);
return true;
} else {
return false;
}
}
removeStreamChildElement(child, force = false) {
if (!force && !this.view.ownsElement(child)) {
return;
}
if (this.streamInserts[child.id]) {
this.streamComponentRestore[child.id] = child;
child.remove();
} else {
if (!this.maybePendingRemove(child)) {
child.remove();
this.onNodeDiscarded(child);
}
}
}
getStreamInsert(el) {
const insert = el.id ? this.streamInserts[el.id] : {};
return insert || {};
}
setStreamRef(el, ref) {
dom_default.putSticky(
el,
PHX_STREAM_REF,
(el2) => el2.setAttribute(PHX_STREAM_REF, ref)
);
}
maybeReOrderStream(el, isNew) {
const { ref, streamAt, reset } = this.getStreamInsert(el);
if (streamAt === void 0) {
return;
}
this.setStreamRef(el, ref);
if (!reset && !isNew) {
return;
}
if (!el.parentElement) {
return;
}
if (streamAt === 0) {
el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
} else if (streamAt > 0) {
const children = Array.from(el.parentElement.children);
const oldIndex = children.indexOf(el);
if (streamAt >= children.length - 1) {
el.parentElement.appendChild(el);
} else {
const sibling = children[streamAt];
if (oldIndex > streamAt) {
el.parentElement.insertBefore(el, sibling);
} else {
el.parentElement.insertBefore(el, sibling.nextElementSibling);
}
}
}
this.maybeLimitStream(el);
}
maybeLimitStream(el) {
const { limit } = this.getStreamInsert(el);
const children = limit !== null && Array.from(el.parentElement.children);
if (limit && limit < 0 && children.length > limit * -1) {
children.slice(0, children.length + limit).forEach((child) => this.removeStreamChildElement(child));
} else if (limit && limit >= 0 && children.length > limit) {
children.slice(limit).forEach((child) => this.removeStreamChildElement(child));
}
}
transitionPendingRemoves() {
const { pendingRemoves, liveSocket } = this;
if (pendingRemoves.length > 0) {
liveSocket.transitionRemoves(pendingRemoves, () => {
pendingRemoves.forEach((el) => {
const child = dom_default.firstPhxChild(el);
if (child) {
liveSocket.destroyViewByEl(child);
}
el.remove();
});
this.trackAfter("transitionsDiscarded", pendingRemoves);
});
}
}
isChangedSelect(fromEl, toEl) {
if (!(fromEl instanceof HTMLSelectElement) || fromEl.multiple) {
return false;
}
if (fromEl.options.length !== toEl.options.length) {
return true;
}
toEl.value = fromEl.value;
return !fromEl.isEqualNode(toEl);
}
isCIDPatch() {
return this.cidPatch;
}
skipCIDSibling(el) {
return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
}
targetCIDContainer(html) {
if (!this.isCIDPatch()) {
return;
}
const [first, ...rest] = dom_default.findComponentNodeList(
this.view.id,
this.targetCID
);
if (rest.length === 0 && dom_default.childNodeLength(html) === 1) {
return first;
} else {
return first && first.parentNode;
}
}
indexOf(parent, child) {
return Array.from(parent.children).indexOf(child);
}
teleport(el, morph) {
const targetSelector = el.getAttribute(PHX_PORTAL);
const portalContainer = document.querySelector(targetSelector);
if (!portalContainer) {
throw new Error(
"portal target with selector " + targetSelector + " not found"
);
}
const toTeleport = el.content.firstElementChild;
if (this.skipCIDSibling(toTeleport)) {
return;
}
if (!toTeleport?.id) {
throw new Error(
"phx-portal template must have a single root element with ID!"
);
}
const existing = document.getElementById(toTeleport.id);
let portalTarget;
if (existing) {
if (!portalContainer.contains(existing)) {
portalContainer.appendChild(existing);
}
portalTarget = existing;
} else {
portalTarget = document.createElement(toTeleport.tagName);
portalContainer.appendChild(portalTarget);
}
toTeleport.setAttribute(PHX_TELEPORTED_REF, this.view.id);
toTeleport.setAttribute(PHX_TELEPORTED_SRC, el.id);
morph(portalTarget, toTeleport, true);
toTeleport.removeAttribute(PHX_TELEPORTED_REF);
toTeleport.removeAttribute(PHX_TELEPORTED_SRC);
this.view.pushPortalElementId(toTeleport.id);
}
handleRuntimeHook(el, source) {
const name = el.getAttribute(PHX_RUNTIME_HOOK);
let nonce = el.hasAttribute("nonce") ? el.getAttribute("nonce") : null;
if (el.hasAttribute("nonce")) {
const template = document.createElement("template");
template.innerHTML = source;
nonce = template.content.querySelector(`script[${PHX_RUNTIME_HOOK}="${CSS.escape(name)}"]`).getAttribute("nonce");
}
const script = document.createElement("script");
script.textContent = el.textContent;
dom_default.mergeAttrs(script, el, { isIgnored: false });
if (nonce) {
script.nonce = nonce;
}
el.replaceWith(script);
el = script;
}
};
var VOID_TAGS = /* @__PURE__ */ new Set([
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
]);
var quoteChars = /* @__PURE__ */ new Set(["'", '"']);
var modifyRoot = (html, attrs, clearInnerHTML) => {
let i = 0;
let insideComment = false;
let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
const lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
if (lookahead === null) {
throw new Error(`malformed html ${html}`);
}
i = lookahead[0].length;
beforeTag = lookahead[1];
tag = lookahead[2];
tagNameEndsAt = i;
for (i; i < html.length; i++) {
if (html.charAt(i) === ">") {
break;
}
if (html.charAt(i) === "=") {
const isId = html.slice(i - 3, i) === " id";
i++;
const char = html.charAt(i);
if (quoteChars.has(char)) {
const attrStartsAt = i;
i++;
for (i; i < html.length; i++) {
if (html.charAt(i) === char) {
break;
}
}
if (isId) {
id = html.slice(attrStartsAt + 1, i);
break;
}
}
}
}
let closeAt = html.length - 1;
insideComment = false;
while (closeAt >= beforeTag.length + tag.length) {
const char = html.charAt(closeAt);
if (insideComment) {
if (char === "-" && html.slice(closeAt - 3, closeAt) === "<!-") {
insideComment = false;
closeAt -= 4;
} else {
closeAt -= 1;
}
} else if (char === ">" && html.slice(closeAt - 2, closeAt) === "--") {
insideComment = true;
closeAt -= 3;
} else if (char === ">") {
break;
} else {
closeAt -= 1;
}
}
afterTag = html.slice(closeAt + 1, html.length);
const attrsStr = Object.keys(attrs).map((attr) => attrs[attr] === true ? attr : `${attr}="${attrs[attr]}"`).join(" ");
if (clearInnerHTML) {
const idAttrStr = id ? ` id="${id}"` : "";
if (VOID_TAGS.has(tag)) {
newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}/>`;
} else {
newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}></${tag}>`;
}
} else {
const rest = html.slice(tagNameEndsAt, closeAt + 1);
newHTML = `<${tag}${attrsStr === "" ? "" : " "}${attrsStr}${rest}`;
}
return [newHTML, beforeTag, afterTag];
};
var Rendered = class {
static extract(diff) {
const { [REPLY]: reply, [EVENTS]: events, [TITLE]: title } = diff;
delete diff[REPLY];
delete diff[EVENTS];
delete diff[TITLE];
return { diff, title, reply: reply || null, events: events || [] };
}
constructor(viewId, rendered) {
this.viewId = viewId;
this.rendered = {};
this.magicId = 0;
this.mergeDiff(rendered);
}
parentViewId() {
return this.viewId;
}
toString(onlyCids) {
const { buffer: str, streams } = this.recursiveToString(
this.rendered,
this.rendered[COMPONENTS],
onlyCids,
true,
{}
);
return { buffer: str, streams };
}
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids, changeTracking, rootAttrs) {
onlyCids = onlyCids ? new Set(onlyCids) : null;
const output = {
buffer: "",
components,
onlyCids,
streams: /* @__PURE__ */ new Set()
};
this.toOutputBuffer(rendered, null, output, changeTracking, rootAttrs);
return { buffer: output.buffer, streams: output.streams };
}
componentCIDs(diff) {
return Object.keys(diff[COMPONENTS] || {}).map((i) => parseInt(i));
}
isComponentOnlyDiff(diff) {
if (!diff[COMPONENTS]) {
return false;
}
return Object.keys(diff).length === 1;
}
getComponent(diff, cid) {
return diff[COMPONENTS][cid];
}
resetRender(cid) {
if (this.rendered[COMPONENTS][cid]) {
this.rendered[COMPONENTS][cid].reset = true;
}
}
mergeDiff(diff) {
const newc = diff[COMPONENTS];
const cache = {};
delete diff[COMPONENTS];
this.rendered = this.mutableMerge(this.rendered, diff);
this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {};
if (newc) {
const oldc = this.rendered[COMPONENTS];
for (const cid in newc) {
newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache);
}
for (const cid in newc) {
oldc[cid] = newc[cid];
}
diff[COMPONENTS] = newc;
}
}
cachedFindComponent(cid, cdiff, oldc, newc, cache) {
if (cache[cid]) {
return cache[cid];
} else {
let ndiff, stat, scid = cdiff[STATIC];
if (isCid(scid)) {
let tdiff;
if (scid > 0) {
tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache);
} else {
tdiff = oldc[-scid];
}
stat = tdiff[STATIC];
ndiff = this.cloneMerge(tdiff, cdiff, true);
ndiff[STATIC] = stat;
} else {
ndiff = cdiff[STATIC] !== void 0 || oldc[cid] === void 0 ? cdiff : this.cloneMerge(oldc[cid], cdiff, false);
}
cache[cid] = ndiff;
return ndiff;
}
}
mutableMerge(target, source) {
if (source[STATIC] !== void 0) {
return source;
} else {
this.doMutableMerge(target, source);
return target;
}
}
doMutableMerge(target, source) {
if (source[KEYED]) {
this.mergeKeyed(target, source);
} else {
for (const key in source) {
const val = source[key];
const targetVal = target[key];
const isObjVal = isObject(val);
if (isObjVal && val[STATIC] === void 0 && isObject(targetVal)) {
this.doMutableMerge(targetVal, val);
} else {
target[key] = val;
}
}
}
if (target[ROOT]) {
target.newRender = true;
}
}
clone(diff) {
if ("structuredClone" in window) {
return structuredClone(diff);
} else {
return JSON.parse(JSON.stringify(diff));
}
}
// keyed comprehensions
mergeKeyed(target, source) {
const clonedTarget = this.clone(target);
Object.entries(source[KEYED]).forEach(([i, entry]) => {
if (i === KEYED_COUNT) {
return;
}
if (Array.isArray(entry)) {
const [old_idx, diff] = entry;
target[KEYED][i] = clonedTarget[KEYED][old_idx];
this.doMutableMerge(target[KEYED][i], diff);
} else if (typeof entry === "number") {
const old_idx = entry;
target[KEYED][i] = clonedTarget[KEYED][old_idx];
} else if (typeof entry === "object") {
if (!target[KEYED][i]) {
target[KEYED][i] = {};
}
this.doMutableMerge(target[KEYED][i], entry);
}
});
if (source[KEYED][KEYED_COUNT] < target[KEYED][KEYED_COUNT]) {
for (let i = source[KEYED][KEYED_COUNT]; i < target[KEYED][KEYED_COUNT]; i++) {
delete target[KEYED][i];
}
}
target[KEYED][KEYED_COUNT] = source[KEYED][KEYED_COUNT];
if (source[STREAM]) {
target[STREAM] = source[STREAM];
}
if (source[TEMPLATES]) {
target[TEMPLATES] = source[TEMPLATES];
}
}
// Merges cid trees together, copying statics from source tree.
//
// The `pruneMagicId` is passed to control pruning the magicId of the
// target. We must always prune the magicId when we are sharing statics
// from another component. If not pruning, we replicate the logic from
// mutableMerge, where we set newRender to true if there is a root
// (effectively forcing the new version to be rendered instead of skipped)
//
cloneMerge(target, source, pruneMagicId) {
let merged;
if (source[KEYED]) {
merged = this.clone(target);
this.mergeKeyed(merged, source);
} else {
merged = { ...target, ...source };
for (const key in merged) {
const val = source[key];
const targetVal = target[key];
if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
merged[key] = this.cloneMerge(targetVal, val, pruneMagicId);
} else if (val === void 0 && isObject(targetVal)) {
merged[key] = this.cloneMerge(targetVal, {}, pruneMagicId);
}
}
}
if (pruneMagicId) {
delete merged.magicId;
delete merged.newRender;
} else if (target[ROOT]) {
merged.newRender = true;
}
return merged;
}
componentToString(cid) {
const { buffer: str, streams } = this.recursiveCIDToString(
this.rendered[COMPONENTS],
cid,
null
);
const [strippedHTML, _before, _after] = modifyRoot(str, {});
return { buffer: strippedHTML, streams };
}
pruneCIDs(cids) {
cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
}
// private
get() {
return this.rendered;
}
isNewFingerprint(diff = {}) {
return !!diff[STATIC];
}
templateStatic(part, templates) {
if (typeof part === "number") {
return templates[part];
} else {
return part;
}
}
nextMagicID() {
this.magicId++;
return `m${this.magicId}-${this.parentViewId()}`;
}
// Converts rendered tree to output buffer.
//
// changeTracking controls if we can apply the PHX_SKIP optimization.
toOutputBuffer(rendered, templates, output, changeTracking, rootAttrs = {}) {
if (rendered[KEYED]) {
return this.comprehensionToBuffer(
rendered,
templates,
output,
changeTracking
);
}
if (rendered[TEMPLATES]) {
templates = rendered[TEMPLATES];
delete rendered[TEMPLATES];
}
let { [STATIC]: statics } = rendered;
statics = this.templateStatic(statics, templates);
rendered[STATIC] = statics;
const isRoot = rendered[ROOT];
const prevBuffer = output.buffer;
if (isRoot) {
output.buffer = "";
}
if (changeTracking && isRoot && !rendered.magicId) {
rendered.newRender = true;
rendered.magicId = this.nextMagicID();
}
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
this.dynamicToBuffer(rendered[i - 1], templates, output, changeTracking);
output.buffer += statics[i];
}
if (isRoot) {
let skip = false;
let attrs;
if (changeTracking || rendered.magicId) {
skip = changeTracking && !rendered.newRender;
attrs = { [PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs };
} else {
attrs = rootAttrs;
}
if (skip) {
attrs[PHX_SKIP] = true;
}
const [newRoot, commentBefore, commentAfter] = modifyRoot(
output.buffer,
attrs,
skip
);
rendered.newRender = false;
output.buffer = prevBuffer + commentBefore + newRoot + commentAfter;
}
}
comprehensionToBuffer(rendered, templates, output, changeTracking) {
const keyedTemplates = templates || rendered[TEMPLATES];
const statics = this.templateStatic(rendered[STATIC], templates);
rendered[STATIC] = statics;
delete rendered[TEMPLATES];
for (let i = 0; i < rendered[KEYED][KEYED_COUNT]; i++) {
output.buffer += statics[0];
for (let j = 1; j < statics.length; j++) {
this.dynamicToBuffer(
rendered[KEYED][i][j - 1],
keyedTemplates,
output,
changeTracking
);
output.buffer += statics[j];
}
}
if (rendered[STREAM]) {
const stream = rendered[STREAM];
const [_ref, _inserts, deleteIds, reset] = stream || [null, {}, [], null];
if (stream !== void 0 && (rendered[KEYED][KEYED_COUNT] > 0 || deleteIds.length > 0 || reset)) {
delete rendered[STREAM];
rendered[KEYED] = {
[KEYED_COUNT]: 0
};
output.streams.add(stream);
}
}
}
dynamicToBuffer(rendered, templates, output, changeTracking) {
if (typeof rendered === "number") {
const { buffer: str, streams } = this.recursiveCIDToString(
output.components,
rendered,
output.onlyCids
);
output.buffer += str;
output.streams = /* @__PURE__ */ new Set([...output.streams, ...streams]);
} else if (isObject(rendered)) {
this.toOutputBuffer(rendered, templates, output, changeTracking, {});
} else {
output.buffer += rendered;
}
}
recursiveCIDToString(components, cid, onlyCids) {
const component = components[cid] || logError(`no component for CID ${cid}`, components);
const attrs = { [PHX_COMPONENT]: cid, [PHX_VIEW_REF]: this.viewId };
const skip = onlyCids && !onlyCids.has(cid);
component.newRender = !skip;
component.magicId = `c${cid}-${this.parentViewId()}`;
const changeTracking = !component.reset;
const { buffer: html, streams } = this.recursiveToString(
component,
components,
onlyCids,
changeTracking,
attrs
);
delete component.reset;
return { buffer: html, streams };
}
};
var focusStack = [];
var default_transition_time = 200;
var JS = {
// private
exec(e, eventType, phxEvent, view, sourceEl, defaults) {
const [defaultKind, defaultArgs] = defaults || [
null,
{ callback: defaults && defaults.callback }
];
const commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
commands.forEach(([kind, args]) => {
if (kind === defaultKind) {
args = { ...defaultArgs, ...args };
args.callback = args.callback || defaultArgs.callback;
}
this.filterToEls(view.liveSocket, sourceEl, args).forEach((el) => {
this[`exec_${kind}`](e, eventType, phxEvent, view, sourceEl, el, args);
});
});
},
isVisible(el) {
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
},
// returns true if any part of the element is inside the viewport
isInViewport(el) {
const rect = el.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
return rect.right > 0 && rect.bottom > 0 && rect.left < windowWidth && rect.top < windowHeight;
},
// private
// commands
exec_exec(e, eventType, phxEvent, view, sourceEl, el, { attr, to }) {
const encodedJS = el.getAttribute(attr);
if (!encodedJS) {
throw new Error(`expected ${attr} to contain JS command on "${to}"`);
}
view.liveSocket.execJS(el, encodedJS, eventType);
},
exec_dispatch(e, eventType, phxEvent, view, sourceEl, el, { event, detail, bubbles, blocking }) {
detail = detail || {};
detail.dispatcher = sourceEl;
if (blocking) {
const promise = new Promise((resolve, _reject) => {
detail.done = resolve;
});
view.liveSocket.asyncTransition(promise);
}
dom_default.dispatchEvent(el, event, { detail, bubbles });
},
exec_push(e, eventType, phxEvent, view, sourceEl, el, args) {
const {
event,
data,
target,
page_loading,
loading,
value,
dispatcher,
callback
} = args;
const pushOpts = {
loading,
value,
target,
page_loading: !!page_loading,
originalEvent: e
};
const targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
const phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
const handler = (targetView, targetCtx) => {
if (!targetView.isConnected()) {
return;
}
if (eventType === "change") {
let { newCid, _target } = args;
_target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
if (_target) {
pushOpts._target = _target;
}
targetView.pushInput(
sourceEl,
targetCtx,
newCid,
event || phxEvent,
pushOpts,
callback
);
} else if (eventType === "submit") {
const { submitter } = args;
targetView.submitForm(
sourceEl,
targetCtx,
event || phxEvent,
submitter,
pushOpts,
callback
);
} else {
targetView.pushEvent(
eventType,
sourceEl,
targetCtx,
event || phxEvent,
data,
pushOpts,
callback
);
}
};
if (args.targetView && args.targetCtx) {
handler(args.targetView, args.targetCtx);
} else {
view.withinTargets(phxTarget, handler);
}
},
exec_navigate(e, eventType, phxEvent, view, sourceEl, el, { href, replace }) {
view.liveSocket.historyRedirect(
e,
href,
replace ? "replace" : "push",
null,
sourceEl
);
},
exec_patch(e, eventType, phxEvent, view, sourceEl, el, { href, replace }) {
view.liveSocket.pushHistoryPatch(
e,
href,
replace ? "replace" : "push",
sourceEl
);
},
exec_focus(e, eventType, phxEvent, view, sourceEl, el) {
aria_default.attemptFocus(el);
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => aria_default.attemptFocus(el));
});
},
exec_focus_first(e, eventType, phxEvent, view, sourceEl, el) {
aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el);
window.requestAnimationFrame(() => {
window.requestAnimationFrame(
() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el)
);
});
},
exec_push_focus(e, eventType, phxEvent, view, sourceEl, el) {
focusStack.push(el || sourceEl);
},
exec_pop_focus(_e, _eventType, _phxEvent, _view, _sourceEl, _el) {
const el = focusStack.pop();
if (el) {
el.focus();
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => el.focus());
});
}
},
exec_add_class(e, eventType, phxEvent, view, sourceEl, el, { names, transition, time, blocking }) {
this.addOrRemoveClasses(el, names, [], transition, time, view, blocking);
},
exec_remove_class(e, eventType, phxEvent, view, sourceEl, el, { names, transition, time, blocking }) {
this.addOrRemoveClasses(el, [], names, transition, time, view, blocking);
},
exec_toggle_class(e, eventType, phxEvent, view, sourceEl, el, { names, transition, time, blocking }) {
this.toggleClasses(el, names, transition, time, view, blocking);
},
exec_toggle_attr(e, eventType, phxEvent, view, sourceEl, el, { attr: [attr, val1, val2] }) {
this.toggleAttr(el, attr, val1, val2);
},
exec_ignore_attrs(e, eventType, phxEvent, view, sourceEl, el, { attrs }) {
this.ignoreAttrs(el, attrs);
},
exec_transition(e, eventType, phxEvent, view, sourceEl, el, { time, transition, blocking }) {
this.addOrRemoveClasses(el, [], [], transition, time, view, blocking);
},
exec_toggle(e, eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time, blocking }) {
this.toggle(eventType, view, el, display, ins, outs, time, blocking);
},
exec_show(e, eventType, phxEvent, view, sourceEl, el, { display, transition, time, blocking }) {
this.show(eventType, view, el, display, transition, time, blocking);
},
exec_hide(e, eventType, phxEvent, view, sourceEl, el, { display, transition, time, blocking }) {
this.hide(eventType, view, el, display, transition, time, blocking);
},
exec_set_attr(e, eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
this.setOrRemoveAttrs(el, [[attr, val]], []);
},
exec_remove_attr(e, eventType, phxEvent, view, sourceEl, el, { attr }) {
this.setOrRemoveAttrs(el, [], [attr]);
},
ignoreAttrs(el, attrs) {
dom_default.putPrivate(el, "JS:ignore_attrs", {
apply: (fromEl, toEl) => {
let fromAttributes = Array.from(fromEl.attributes);
let fromAttributeNames = fromAttributes.map((attr) => attr.name);
Array.from(toEl.attributes).filter((attr) => {
return !fromAttributeNames.includes(attr.name);
}).forEach((attr) => {
if (dom_default.attributeIgnored(attr, attrs)) {
toEl.removeAttribute(attr.name);
}
});
fromAttributes.forEach((attr) => {
if (dom_default.attributeIgnored(attr, attrs)) {
toEl.setAttribute(attr.name, attr.value);
}
});
}
});
},
onBeforeElUpdated(fromEl, toEl) {
const ignoreAttrs = dom_default.private(fromEl, "JS:ignore_attrs");
if (ignoreAttrs) {
ignoreAttrs.apply(fromEl, toEl);
}
},
// utils for commands
show(eventType, view, el, display, transition, time, blocking) {
if (!this.isVisible(el)) {
this.toggle(
eventType,
view,
el,
display,
transition,
null,
time,
blocking
);
}
},
hide(eventType, view, el, display, transition, time, blocking) {
if (this.isVisible(el)) {
this.toggle(
eventType,
view,
el,
display,
null,
transition,
time,
blocking
);
}
},
toggle(eventType, view, el, display, ins, outs, time, blocking) {
time = time || default_transition_time;
const [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
const [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
if (inClasses.length > 0 || outClasses.length > 0) {
if (this.isVisible(el)) {
const onStart = () => {
this.addOrRemoveClasses(
el,
outStartClasses,
inClasses.concat(inStartClasses).concat(inEndClasses)
);
window.requestAnimationFrame(() => {
this.addOrRemoveClasses(el, outClasses, []);
window.requestAnimationFrame(
() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses)
);
});
};
const onEnd = () => {
this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
dom_default.putSticky(
el,
"toggle",
(currentEl) => currentEl.style.display = "none"
);
el.dispatchEvent(new Event("phx:hide-end"));
};
el.dispatchEvent(new Event("phx:hide-start"));
if (blocking === false) {
onStart();
setTimeout(onEnd, time);
} else {
view.transition(time, onStart, onEnd);
}
} else {
if (eventType === "remove") {
return;
}
const onStart = () => {
this.addOrRemoveClasses(
el,
inStartClasses,
outClasses.concat(outStartClasses).concat(outEndClasses)
);
const stickyDisplay = display || this.defaultDisplay(el);
window.requestAnimationFrame(() => {
this.addOrRemoveClasses(el, inClasses, []);
window.requestAnimationFrame(() => {
dom_default.putSticky(
el,
"toggle",
(currentEl) => currentEl.style.display = stickyDisplay
);
this.addOrRemoveClasses(el, inEndClasses, inStartClasses);
});
});
};
const onEnd = () => {
this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
el.dispatchEvent(new Event("phx:show-end"));
};
el.dispatchEvent(new Event("phx:show-start"));
if (blocking === false) {
onStart();
setTimeout(onEnd, time);
} else {
view.transition(time, onStart, onEnd);
}
}
} else {
if (this.isVisible(el)) {
window.requestAnimationFrame(() => {
el.dispatchEvent(new Event("phx:hide-start"));
dom_default.putSticky(
el,
"toggle",
(currentEl) => currentEl.style.display = "none"
);
el.dispatchEvent(new Event("phx:hide-end"));
});
} else {
window.requestAnimationFrame(() => {
el.dispatchEvent(new Event("phx:show-start"));
const stickyDisplay = display || this.defaultDisplay(el);
dom_default.putSticky(
el,
"toggle",
(currentEl) => currentEl.style.display = stickyDisplay
);
el.dispatchEvent(new Event("phx:show-end"));
});
}
}
},
toggleClasses(el, classes, transition, time, view, blocking) {
window.requestAnimationFrame(() => {
const [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
const newAdds = classes.filter(
(name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name)
);
const newRemoves = classes.filter(
(name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name)
);
this.addOrRemoveClasses(
el,
newAdds,
newRemoves,
transition,
time,
view,
blocking
);
});
},
toggleAttr(el, attr, val1, val2) {
if (el.hasAttribute(attr)) {
if (val2 !== void 0) {
if (el.getAttribute(attr) === val1) {
this.setOrRemoveAttrs(el, [[attr, val2]], []);
} else {
this.setOrRemoveAttrs(el, [[attr, val1]], []);
}
} else {
this.setOrRemoveAttrs(el, [], [attr]);
}
} else {
this.setOrRemoveAttrs(el, [[attr, val1]], []);
}
},
addOrRemoveClasses(el, adds, removes, transition, time, view, blocking) {
time = time || default_transition_time;
const [transitionRun, transitionStart, transitionEnd] = transition || [
[],
[],
[]
];
if (transitionRun.length > 0) {
const onStart = () => {
this.addOrRemoveClasses(
el,
transitionStart,
[].concat(transitionRun).concat(transitionEnd)
);
window.requestAnimationFrame(() => {
this.addOrRemoveClasses(el, transitionRun, []);
window.requestAnimationFrame(
() => this.addOrRemoveClasses(el, transitionEnd, transitionStart)
);
});
};
const onDone = () => this.addOrRemoveClasses(
el,
adds.concat(transitionEnd),
removes.concat(transitionRun).concat(transitionStart)
);
if (blocking === false) {
onStart();
setTimeout(onDone, time);
} else {
view.transition(time, onStart, onDone);
}
return;
}
window.requestAnimationFrame(() => {
const [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
const keepAdds = adds.filter(
(name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name)
);
const keepRemoves = removes.filter(
(name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name)
);
const newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
const newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
dom_default.putSticky(el, "classes", (currentEl) => {
currentEl.classList.remove(...newRemoves);
currentEl.classList.add(...newAdds);
return [newAdds, newRemoves];
});
});
},
setOrRemoveAttrs(el, sets, removes) {
const [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
const alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
const newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
const newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
dom_default.putSticky(el, "attrs", (currentEl) => {
newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
return [newSets, newRemoves];
});
},
hasAllClasses(el, classes) {
return classes.every((name) => el.classList.contains(name));
},
isToggledOut(el, outClasses) {
return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
},
filterToEls(liveSocket, sourceEl, { to }) {
const defaultQuery = () => {
if (typeof to === "string") {
return document.querySelectorAll(to);
} else if (to.closest) {
const toEl = sourceEl.closest(to.closest);
return toEl ? [toEl] : [];
} else if (to.inner) {
return sourceEl.querySelectorAll(to.inner);
}
};
return to ? liveSocket.jsQuerySelectorAll(sourceEl, to, defaultQuery) : [sourceEl];
},
defaultDisplay(el) {
return { tr: "table-row", td: "table-cell" }[el.tagName.toLowerCase()] || "block";
},
transitionClasses(val) {
if (!val) {
return null;
}
let [trans, tStart, tEnd] = Array.isArray(val) ? val : [val.split(" "), [], []];
trans = Array.isArray(trans) ? trans : trans.split(" ");
tStart = Array.isArray(tStart) ? tStart : tStart.split(" ");
tEnd = Array.isArray(tEnd) ? tEnd : tEnd.split(" ");
return [trans, tStart, tEnd];
}
};
var js_default = JS;
var js_commands_default = (liveSocket, eventType) => {
return {
exec(el, encodedJS) {
liveSocket.execJS(el, encodedJS, eventType);
},
show(el, opts = {}) {
const owner = liveSocket.owner(el);
js_default.show(
eventType,
owner,
el,
opts.display,
js_default.transitionClasses(opts.transition),
opts.time,
opts.blocking
);
},
hide(el, opts = {}) {
const owner = liveSocket.owner(el);
js_default.hide(
eventType,
owner,
el,
null,
js_default.transitionClasses(opts.transition),
opts.time,
opts.blocking
);
},
toggle(el, opts = {}) {
const owner = liveSocket.owner(el);
const inTransition = js_default.transitionClasses(opts.in);
const outTransition = js_default.transitionClasses(opts.out);
js_default.toggle(
eventType,
owner,
el,
opts.display,
inTransition,
outTransition,
opts.time,
opts.blocking
);
},
addClass(el, names, opts = {}) {
const classNames = Array.isArray(names) ? names : names.split(" ");
const owner = liveSocket.owner(el);
js_default.addOrRemoveClasses(
el,
classNames,
[],
js_default.transitionClasses(opts.transition),
opts.time,
owner,
opts.blocking
);
},
removeClass(el, names, opts = {}) {
const classNames = Array.isArray(names) ? names : names.split(" ");
const owner = liveSocket.owner(el);
js_default.addOrRemoveClasses(
el,
[],
classNames,
js_default.transitionClasses(opts.transition),
opts.time,
owner,
opts.blocking
);
},
toggleClass(el, names, opts = {}) {
const classNames = Array.isArray(names) ? names : names.split(" ");
const owner = liveSocket.owner(el);
js_default.toggleClasses(
el,
classNames,
js_default.transitionClasses(opts.transition),
opts.time,
owner,
opts.blocking
);
},
transition(el, transition, opts = {}) {
const owner = liveSocket.owner(el);
js_default.addOrRemoveClasses(
el,
[],
[],
js_default.transitionClasses(transition),
opts.time,
owner,
opts.blocking
);
},
setAttribute(el, attr, val) {
js_default.setOrRemoveAttrs(el, [[attr, val]], []);
},
removeAttribute(el, attr) {
js_default.setOrRemoveAttrs(el, [], [attr]);
},
toggleAttribute(el, attr, val1, val2) {
js_default.toggleAttr(el, attr, val1, val2);
},
push(el, type, opts = {}) {
liveSocket.withinOwners(el, (view) => {
const data = opts.value || {};
delete opts.value;
let e = new CustomEvent("phx:exec", { detail: { sourceElement: el } });
js_default.exec(e, eventType, type, view, el, ["push", { data, ...opts }]);
});
},
navigate(href, opts = {}) {
const customEvent = new CustomEvent("phx:exec");
liveSocket.historyRedirect(
customEvent,
href,
opts.replace ? "replace" : "push",
null,
null
);
},
patch(href, opts = {}) {
const customEvent = new CustomEvent("phx:exec");
liveSocket.pushHistoryPatch(
customEvent,
href,
opts.replace ? "replace" : "push",
null
);
},
ignoreAttributes(el, attrs) {
js_default.ignoreAttrs(el, Array.isArray(attrs) ? attrs : [attrs]);
}
};
};
var HOOK_ID = "hookId";
var DEAD_HOOK = "deadHook";
var viewHookID = 1;
var ViewHook = class _ViewHook {
get liveSocket() {
return this.__liveSocket();
}
static makeID() {
return viewHookID++;
}
static elementID(el) {
return dom_default.private(el, HOOK_ID);
}
static deadHook(el) {
return dom_default.private(el, DEAD_HOOK) === true;
}
constructor(view, el, callbacks) {
this.el = el;
this.__attachView(view);
this.__listeners = /* @__PURE__ */ new Set();
this.__isDisconnected = false;
dom_default.putPrivate(this.el, HOOK_ID, _ViewHook.makeID());
if (view && view.isDead) {
dom_default.putPrivate(this.el, DEAD_HOOK, true);
}
if (callbacks) {
const protectedProps = /* @__PURE__ */ new Set([
"el",
"liveSocket",
"__view",
"__listeners",
"__isDisconnected",
"constructor",
// Standard object properties
// Core ViewHook API methods
"js",
"pushEvent",
"pushEventTo",
"handleEvent",
"removeHandleEvent",
"upload",
"uploadTo",
// Internal lifecycle callers
"__mounted",
"__updated",
"__beforeUpdate",
"__destroyed",
"__reconnected",
"__disconnected",
"__cleanup__"
]);
for (const key in callbacks) {
if (Object.prototype.hasOwnProperty.call(callbacks, key)) {
this[key] = callbacks[key];
if (protectedProps.has(key)) {
console.warn(
`Hook object for element #${el.id} overwrites core property '${key}'!`
);
}
}
}
const lifecycleMethods = [
"mounted",
"beforeUpdate",
"updated",
"destroyed",
"disconnected",
"reconnected"
];
lifecycleMethods.forEach((methodName) => {
if (callbacks[methodName] && typeof callbacks[methodName] === "function") {
this[methodName] = callbacks[methodName];
}
});
}
}
/** @internal */
__attachView(view) {
if (view) {
this.__view = () => view;
this.__liveSocket = () => view.liveSocket;
} else {
this.__view = () => {
throw new Error(
`hook not yet attached to a live view: ${this.el.outerHTML}`
);
};
this.__liveSocket = () => {
throw new Error(
`hook not yet attached to a live view: ${this.el.outerHTML}`
);
};
}
}
// Default lifecycle methods
mounted() {
}
beforeUpdate() {
}
updated() {
}
destroyed() {
}
disconnected() {
}
reconnected() {
}
// Internal lifecycle callers - called by the View
/** @internal */
__mounted() {
this.mounted();
}
/** @internal */
__updated() {
this.updated();
}
/** @internal */
__beforeUpdate() {
this.beforeUpdate();
}
/** @internal */
__destroyed() {
this.destroyed();
dom_default.deletePrivate(this.el, HOOK_ID);
}
/** @internal */
__reconnected() {
if (this.__isDisconnected) {
this.__isDisconnected = false;
this.reconnected();
}
}
/** @internal */
__disconnected() {
this.__isDisconnected = true;
this.disconnected();
}
js() {
return {
...js_commands_default(this.__view().liveSocket, "hook"),
exec: (encodedJS) => {
this.__view().liveSocket.execJS(this.el, encodedJS, "hook");
}
};
}
pushEvent(event, payload, onReply) {
const promise = this.__view().pushHookEvent(
this.el,
null,
event,
payload || {}
);
if (onReply === void 0) {
return promise.then(({ reply }) => reply);
}
promise.then(
({ reply, ref }) => onReply(reply, ref)
).catch(() => {
});
}
pushEventTo(selectorOrTarget, event, payload, onReply) {
if (onReply === void 0) {
const targetPair = [];
this.__view().withinTargets(
selectorOrTarget,
(view, targetCtx) => {
targetPair.push({ view, targetCtx });
}
);
const promises = targetPair.map(({ view, targetCtx }) => {
return view.pushHookEvent(this.el, targetCtx, event, payload || {});
});
return Promise.allSettled(promises);
}
this.__view().withinTargets(
selectorOrTarget,
(view, targetCtx) => {
view.pushHookEvent(this.el, targetCtx, event, payload || {}).then(
({ reply, ref }) => onReply(reply, ref)
).catch(() => {
});
}
);
}
handleEvent(event, callback) {
const callbackRef = {
event,
callback: (customEvent) => callback(customEvent.detail)
};
window.addEventListener(
`phx:${event}`,
callbackRef.callback
);
this.__listeners.add(callbackRef);
return callbackRef;
}
removeHandleEvent(ref) {
window.removeEventListener(
`phx:${ref.event}`,
ref.callback
);
this.__listeners.delete(ref);
}
upload(name, files) {
return this.__view().dispatchUploads(null, name, files);
}
uploadTo(selectorOrTarget, name, files) {
return this.__view().withinTargets(
selectorOrTarget,
(view, targetCtx) => {
view.dispatchUploads(targetCtx, name, files);
}
);
}
/** @internal */
__cleanup__() {
this.__listeners.forEach(
(callbackRef) => this.removeHandleEvent(callbackRef)
);
}
};
var prependFormDataKey = (key, prefix) => {
const isArray = key.endsWith("[]");
let baseKey = isArray ? key.slice(0, -2) : key;
baseKey = baseKey.replace(/([^\[\]]+)(\]?$)/, `${prefix}$1$2`);
if (isArray) {
baseKey += "[]";
}
return baseKey;
};
var serializeForm = (form, opts, onlyNames = []) => {
const { submitter } = opts;
let injectedElement;
if (submitter && submitter.name) {
const input = document.createElement("input");
input.type = "hidden";
const formId = submitter.getAttribute("form");
if (formId) {
input.setAttribute("form", formId);
}
input.name = submitter.name;
input.value = submitter.value;
submitter.parentElement.insertBefore(input, submitter);
injectedElement = input;
}
const formData = new FormData(form);
const toRemove = [];
formData.forEach((val, key, _index) => {
if (val instanceof File) {
toRemove.push(key);
}
});
toRemove.forEach((key) => formData.delete(key));
const params = new URLSearchParams();
const { inputsUnused, onlyHiddenInputs } = Array.from(form.elements).reduce(
(acc, input) => {
const { inputsUnused: inputsUnused2, onlyHiddenInputs: onlyHiddenInputs2 } = acc;
const key = input.name;
if (!key) {
return acc;
}
if (inputsUnused2[key] === void 0) {
inputsUnused2[key] = true;
}
if (onlyHiddenInputs2[key] === void 0) {
onlyHiddenInputs2[key] = true;
}
const isUsed = dom_default.private(input, PHX_HAS_FOCUSED) || dom_default.private(input, PHX_HAS_SUBMITTED);
const isHidden = input.type === "hidden";
inputsUnused2[key] = inputsUnused2[key] && !isUsed;
onlyHiddenInputs2[key] = onlyHiddenInputs2[key] && isHidden;
return acc;
},
{ inputsUnused: {}, onlyHiddenInputs: {} }
);
for (const [key, val] of formData.entries()) {
if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
const isUnused = inputsUnused[key];
const hidden = onlyHiddenInputs[key];
if (isUnused && !(submitter && submitter.name == key) && !hidden) {
params.append(prependFormDataKey(key, "_unused_"), "");
}
if (typeof val === "string") {
params.append(key, val);
}
}
}
if (submitter && injectedElement) {
submitter.parentElement.removeChild(injectedElement);
}
return params.toString();
};
var View = class _View {
static closestView(el) {
const liveViewEl = el.closest(PHX_VIEW_SELECTOR);
return liveViewEl ? dom_default.private(liveViewEl, "view") : null;
}
constructor(el, liveSocket, parentView, flash, liveReferer) {
this.isDead = false;
this.liveSocket = liveSocket;
this.flash = flash;
this.parent = parentView;
this.root = parentView ? parentView.root : this;
this.el = el;
const boundView = dom_default.private(this.el, "view");
if (boundView !== void 0 && boundView.isDead !== true) {
logError(
`The DOM element for this view has already been bound to a view.
An element can only ever be associated with a single view!
Please ensure that you are not trying to initialize multiple LiveSockets on the same page.
This could happen if you're accidentally trying to render your root layout more than once.
Ensure that the template set on the LiveView is different than the root layout.
`,
{ view: boundView }
);
throw new Error("Cannot bind multiple views to the same DOM element.");
}
dom_default.putPrivate(this.el, "view", this);
this.id = this.el.id;
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
this.ref = 0;
this.lastAckRef = null;
this.childJoins = 0;
this.loaderTimer = null;
this.disconnectedTimer = null;
this.pendingDiffs = [];
this.pendingForms = /* @__PURE__ */ new Set();
this.redirect = false;
this.href = null;
this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
this.joinAttempts = 0;
this.joinPending = true;
this.destroyed = false;
this.joinCallback = function(onDone) {
onDone && onDone();
};
this.stopCallback = function() {
};
this.pendingJoinOps = [];
this.viewHooks = {};
this.formSubmits = [];
this.children = this.parent ? null : {};
this.root.children[this.id] = {};
this.formsForRecovery = {};
this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
const url = this.href && this.expandURL(this.href);
return {
redirect: this.redirect ? url : void 0,
url: this.redirect ? void 0 : url || void 0,
params: this.connectParams(liveReferer),
session: this.getSession(),
static: this.getStatic(),
flash: this.flash,
sticky: this.el.hasAttribute(PHX_STICKY)
};
});
this.portalElementIds = /* @__PURE__ */ new Set();
}
setHref(href) {
this.href = href;
}
setRedirect(href) {
this.redirect = true;
this.href = href;
}
isMain() {
return this.el.hasAttribute(PHX_MAIN);
}
connectParams(liveReferer) {
const params = this.liveSocket.params(this.el);
const manifest = dom_default.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`).map((node) => node.src || node.href).filter((url) => typeof url === "string");
if (manifest.length > 0) {
params["_track_static"] = manifest;
}
params["_mounts"] = this.joinCount;
params["_mount_attempts"] = this.joinAttempts;
params["_live_referer"] = liveReferer;
this.joinAttempts++;
return params;
}
isConnected() {
return this.channel.canPush();
}
getSession() {
return this.el.getAttribute(PHX_SESSION);
}
getStatic() {
const val = this.el.getAttribute(PHX_STATIC);
return val === "" ? null : val;
}
destroy(callback = function() {
}) {
this.destroyAllChildren();
this.destroyPortalElements();
this.destroyed = true;
dom_default.deletePrivate(this.el, "view");
delete this.root.children[this.id];
if (this.parent) {
delete this.root.children[this.parent.id][this.id];
}
clearTimeout(this.loaderTimer);
const onFinished = () => {
callback();
for (const id in this.viewHooks) {
this.destroyHook(this.viewHooks[id]);
}
};
dom_default.markPhxChildDestroyed(this.el);
this.log("destroyed", () => ["the child has been removed from the parent"]);
this.channel.leave().receive("ok", onFinished).receive("error", onFinished).receive("timeout", onFinished);
}
setContainerClasses(...classes) {
this.el.classList.remove(
PHX_CONNECTED_CLASS,
PHX_LOADING_CLASS,
PHX_ERROR_CLASS,
PHX_CLIENT_ERROR_CLASS,
PHX_SERVER_ERROR_CLASS
);
this.el.classList.add(...classes);
}
showLoader(timeout) {
clearTimeout(this.loaderTimer);
if (timeout) {
this.loaderTimer = setTimeout(() => this.showLoader(), timeout);
} else {
for (const id in this.viewHooks) {
this.viewHooks[id].__disconnected();
}
this.setContainerClasses(PHX_LOADING_CLASS);
}
}
execAll(binding) {
dom_default.all(
this.el,
`[${binding}]`,
(el) => this.liveSocket.execJS(el, el.getAttribute(binding))
);
}
hideLoader() {
clearTimeout(this.loaderTimer);
clearTimeout(this.disconnectedTimer);
this.setContainerClasses(PHX_CONNECTED_CLASS);
this.execAll(this.binding("connected"));
}
triggerReconnected() {
for (const id in this.viewHooks) {
this.viewHooks[id].__reconnected();
}
}
log(kind, msgCallback) {
this.liveSocket.log(this, kind, msgCallback);
}
transition(time, onStart, onDone = function() {
}) {
this.liveSocket.transition(time, onStart, onDone);
}
// calls the callback with the view and target element for the given phxTarget
// targets can be:
// * an element itself, then it is simply passed to liveSocket.owner;
// * a CID (Component ID), then we first search the component's element in the DOM
// * a selector, then we search the selector in the DOM and call the callback
// for each element found with the corresponding owner view
withinTargets(phxTarget, callback, dom = document) {
if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
return this.liveSocket.owner(
phxTarget,
(view) => callback(view, phxTarget)
);
}
if (isCid(phxTarget)) {
const targets = dom_default.findComponentNodeList(this.id, phxTarget, dom);
if (targets.length === 0) {
logError(`no component found matching phx-target of ${phxTarget}`);
} else {
callback(this, parseInt(phxTarget));
}
} else {
const targets = Array.from(dom.querySelectorAll(phxTarget));
if (targets.length === 0) {
logError(
`nothing found matching the phx-target selector "${phxTarget}"`
);
}
targets.forEach(
(target) => this.liveSocket.owner(target, (view) => callback(view, target))
);
}
}
applyDiff(type, rawDiff, callback) {
this.log(type, () => ["", clone(rawDiff)]);
const { diff, reply, events, title } = Rendered.extract(rawDiff);
const ev = events.reduce(
(acc, args) => {
if (args.length === 3 && args[2] == true) {
acc.pre.push(args.slice(0, -1));
} else {
acc.post.push(args);
}
return acc;
},
{ pre: [], post: [] }
);
this.liveSocket.dispatchEvents(ev.pre);
const update = () => {
callback({ diff, reply, events: ev.post });
if (typeof title === "string" || type == "mount" && this.isMain()) {
window.requestAnimationFrame(() => dom_default.putTitle(title));
}
};
if ("onDocumentPatch" in this.liveSocket.domCallbacks) {
this.liveSocket.triggerDOM("onDocumentPatch", [update]);
} else {
update();
}
}
onJoin(resp) {
const { rendered, container, liveview_version, pid } = resp;
if (container) {
const [tag, attrs] = container;
this.el = dom_default.replaceRootContainer(this.el, tag, attrs);
}
this.childJoins = 0;
this.joinPending = true;
this.flash = null;
if (this.root === this) {
this.formsForRecovery = this.getFormsForRecovery();
}
if (this.isMain() && window.history.state === null) {
browser_default.pushState("replace", {
type: "patch",
id: this.id,
position: this.liveSocket.currentHistoryPosition
});
}
if (liveview_version !== this.liveSocket.version()) {
console.warn(
`LiveView asset version mismatch. JavaScript version ${this.liveSocket.version()} vs. server ${liveview_version}. To avoid issues, please ensure that your assets use the same version as the server.`
);
}
if (pid) {
this.el.setAttribute(PHX_LV_PID, pid);
}
browser_default.dropLocal(
this.liveSocket.localStorage,
window.location.pathname,
CONSECUTIVE_RELOADS
);
this.applyDiff("mount", rendered, ({ diff, events }) => {
this.rendered = new Rendered(this.id, diff);
const [html, streams] = this.renderContainer(null, "join");
this.dropPendingRefs();
this.joinCount++;
this.joinAttempts = 0;
this.maybeRecoverForms(html, () => {
this.onJoinComplete(resp, html, streams, events);
});
});
}
dropPendingRefs() {
dom_default.all(document, `[${PHX_REF_SRC}="${this.refSrc()}"]`, (el) => {
el.removeAttribute(PHX_REF_LOADING);
el.removeAttribute(PHX_REF_SRC);
el.removeAttribute(PHX_REF_LOCK);
});
}
onJoinComplete({ live_patch }, html, streams, events) {
if (this.joinCount > 1 || this.parent && !this.parent.isJoinPending()) {
return this.applyJoinPatch(live_patch, html, streams, events);
}
const newChildren = dom_default.findPhxChildrenInFragment(html, this.id).filter(
(toEl) => {
const fromEl = toEl.id && this.el.querySelector(`[id="${toEl.id}"]`);
const phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC);
if (phxStatic) {
toEl.setAttribute(PHX_STATIC, phxStatic);
}
if (fromEl) {
fromEl.setAttribute(PHX_ROOT_ID, this.root.id);
}
return this.joinChild(toEl);
}
);
if (newChildren.length === 0) {
if (this.parent) {
this.root.pendingJoinOps.push([
this,
() => this.applyJoinPatch(live_patch, html, streams, events)
]);
this.parent.ackJoin(this);
} else {
this.onAllChildJoinsComplete();
this.applyJoinPatch(live_patch, html, streams, events);
}
} else {
this.root.pendingJoinOps.push([
this,
() => this.applyJoinPatch(live_patch, html, streams, events)
]);
}
}
attachTrueDocEl() {
this.el = dom_default.byId(this.id);
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
}
// this is invoked for dead and live views, so we must filter by
// by owner to ensure we aren't duplicating hooks across disconnect
// and connected states. This also handles cases where hooks exist
// in a root layout with a LV in the body
execNewMounted(parent = document) {
let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
this.all(
parent,
`[${phxViewportTop}], [${phxViewportBottom}]`,
(hookEl) => {
dom_default.maintainPrivateHooks(
hookEl,
hookEl,
phxViewportTop,
phxViewportBottom
);
this.maybeAddNewHook(hookEl);
}
);
this.all(
parent,
`[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`,
(hookEl) => {
this.maybeAddNewHook(hookEl);
}
);
this.all(parent, `[${this.binding(PHX_MOUNTED)}]`, (el) => {
this.maybeMounted(el);
});
}
all(parent, selector, callback) {
dom_default.all(parent, selector, (el) => {
if (this.ownsElement(el)) {
callback(el);
}
});
}
applyJoinPatch(live_patch, html, streams, events) {
if (this.joinCount > 1) {
if (this.pendingJoinOps.length) {
this.pendingJoinOps.forEach((cb) => typeof cb === "function" && cb());
this.pendingJoinOps = [];
}
}
this.attachTrueDocEl();
const patch = new DOMPatch(this, this.el, this.id, html, streams, null);
patch.markPrunableContentForRemoval();
this.performPatch(patch, false, true);
this.joinNewChildren();
this.execNewMounted();
this.joinPending = false;
this.liveSocket.dispatchEvents(events);
this.applyPendingUpdates();
if (live_patch) {
const { kind, to } = live_patch;
this.liveSocket.historyPatch(to, kind);
}
this.hideLoader();
if (this.joinCount > 1) {
this.triggerReconnected();
}
this.stopCallback();
}
triggerBeforeUpdateHook(fromEl, toEl) {
this.liveSocket.triggerDOM("onBeforeElUpdated", [fromEl, toEl]);
const hook = this.getHook(fromEl);
const isIgnored = hook && dom_default.isIgnored(fromEl, this.binding(PHX_UPDATE));
if (hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))) {
hook.__beforeUpdate();
return hook;
}
}
maybeMounted(el) {
const phxMounted = el.getAttribute(this.binding(PHX_MOUNTED));
const hasBeenInvoked = phxMounted && dom_default.private(el, "mounted");
if (phxMounted && !hasBeenInvoked) {
this.liveSocket.execJS(el, phxMounted);
dom_default.putPrivate(el, "mounted", true);
}
}
maybeAddNewHook(el) {
const newHook = this.addHook(el);
if (newHook) {
newHook.__mounted();
}
}
performPatch(patch, pruneCids, isJoinPatch = false) {
const removedEls = [];
let phxChildrenAdded = false;
const updatedHookIds = /* @__PURE__ */ new Set();
this.liveSocket.triggerDOM("onPatchStart", [patch.targetContainer]);
patch.after("added", (el) => {
this.liveSocket.triggerDOM("onNodeAdded", [el]);
const phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
const phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
dom_default.maintainPrivateHooks(el, el, phxViewportTop, phxViewportBottom);
this.maybeAddNewHook(el);
if (el.getAttribute) {
this.maybeMounted(el);
}
});
patch.after("phxChildAdded", (el) => {
if (dom_default.isPhxSticky(el)) {
this.liveSocket.joinRootViews();
} else {
phxChildrenAdded = true;
}
});
patch.before("updated", (fromEl, toEl) => {
const hook = this.triggerBeforeUpdateHook(fromEl, toEl);
if (hook) {
updatedHookIds.add(fromEl.id);
}
js_default.onBeforeElUpdated(fromEl, toEl);
});
patch.after("updated", (el) => {
if (updatedHookIds.has(el.id)) {
this.getHook(el).__updated();
}
});
patch.after("discarded", (el) => {
if (el.nodeType === Node.ELEMENT_NODE) {
removedEls.push(el);
}
});
patch.after(
"transitionsDiscarded",
(els) => this.afterElementsRemoved(els, pruneCids)
);
patch.perform(isJoinPatch);
this.afterElementsRemoved(removedEls, pruneCids);
this.liveSocket.triggerDOM("onPatchEnd", [patch.targetContainer]);
return phxChildrenAdded;
}
afterElementsRemoved(elements, pruneCids) {
const destroyedCIDs = [];
elements.forEach((parent) => {
const components = dom_default.all(
parent,
`[${PHX_VIEW_REF}="${this.id}"][${PHX_COMPONENT}]`
);
const hooks = dom_default.all(
parent,
`[${this.binding(PHX_HOOK)}], [data-phx-hook]`
);
components.concat(parent).forEach((el) => {
const cid = this.componentID(el);
if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1 && el.getAttribute(PHX_VIEW_REF) === this.id) {
destroyedCIDs.push(cid);
}
});
hooks.concat(parent).forEach((hookEl) => {
const hook = this.getHook(hookEl);
hook && this.destroyHook(hook);
});
});
if (pruneCids) {
this.maybePushComponentsDestroyed(destroyedCIDs);
}
}
joinNewChildren() {
dom_default.findPhxChildren(document, this.id).forEach((el) => this.joinChild(el));
}
maybeRecoverForms(html, callback) {
const phxChange = this.binding("change");
const oldForms = this.root.formsForRecovery;
const template = document.createElement("template");
template.innerHTML = html;
dom_default.all(template.content, `[${PHX_PORTAL}]`).forEach((portalTemplate) => {
template.content.firstElementChild.appendChild(
portalTemplate.content.firstElementChild
);
});
const rootEl = template.content.firstElementChild;
rootEl.id = this.id;
rootEl.setAttribute(PHX_ROOT_ID, this.root.id);
rootEl.setAttribute(PHX_SESSION, this.getSession());
rootEl.setAttribute(PHX_STATIC, this.getStatic());
rootEl.setAttribute(PHX_PARENT_ID, this.parent ? this.parent.id : null);
const formsToRecover = (
// we go over all forms in the new DOM; because this is only the HTML for the current
// view, we can be sure that all forms are owned by this view:
dom_default.all(template.content, "form").filter((newForm) => newForm.id && oldForms[newForm.id]).filter((newForm) => !this.pendingForms.has(newForm.id)).filter(
(newForm) => oldForms[newForm.id].getAttribute(phxChange) === newForm.getAttribute(phxChange)
).map((newForm) => {
return [oldForms[newForm.id], newForm];
})
);
if (formsToRecover.length === 0) {
return callback();
}
formsToRecover.forEach(([oldForm, newForm], i) => {
this.pendingForms.add(newForm.id);
this.pushFormRecovery(
oldForm,
newForm,
template.content.firstElementChild,
() => {
this.pendingForms.delete(newForm.id);
if (i === formsToRecover.length - 1) {
callback();
}
}
);
});
}
getChildById(id) {
return this.root.children[this.id][id];
}
getDescendentByEl(el) {
if (el.id === this.id) {
return this;
} else {
return this.children[el.getAttribute(PHX_PARENT_ID)]?.[el.id];
}
}
destroyDescendent(id) {
for (const parentId in this.root.children) {
for (const childId in this.root.children[parentId]) {
if (childId === id) {
return this.root.children[parentId][childId].destroy();
}
}
}
}
joinChild(el) {
const child = this.getChildById(el.id);
if (!child) {
const view = new _View(el, this.liveSocket, this);
this.root.children[this.id][view.id] = view;
view.join();
this.childJoins++;
return true;
}
}
isJoinPending() {
return this.joinPending;
}
ackJoin(_child) {
this.childJoins--;
if (this.childJoins === 0) {
if (this.parent) {
this.parent.ackJoin(this);
} else {
this.onAllChildJoinsComplete();
}
}
}
onAllChildJoinsComplete() {
this.pendingForms.clear();
this.formsForRecovery = {};
this.joinCallback(() => {
this.pendingJoinOps.forEach(([view, op]) => {
if (!view.isDestroyed()) {
op();
}
});
this.pendingJoinOps = [];
});
}
update(diff, events, isPending = false) {
if (this.isJoinPending() || this.liveSocket.hasPendingLink() && this.root.isMain()) {
if (!isPending) {
this.pendingDiffs.push({ diff, events });
}
return false;
}
this.rendered.mergeDiff(diff);
let phxChildrenAdded = false;
if (this.rendered.isComponentOnlyDiff(diff)) {
this.liveSocket.time("component patch complete", () => {
const parentCids = dom_default.findExistingParentCIDs(
this.id,
this.rendered.componentCIDs(diff)
);
parentCids.forEach((parentCID) => {
if (this.componentPatch(
this.rendered.getComponent(diff, parentCID),
parentCID
)) {
phxChildrenAdded = true;
}
});
});
} else if (!isEmpty(diff)) {
this.liveSocket.time("full patch complete", () => {
const [html, streams] = this.renderContainer(diff, "update");
const patch = new DOMPatch(this, this.el, this.id, html, streams, null);
phxChildrenAdded = this.performPatch(patch, true);
});
}
this.liveSocket.dispatchEvents(events);
if (phxChildrenAdded) {
this.joinNewChildren();
}
return true;
}
renderContainer(diff, kind) {
return this.liveSocket.time(`toString diff (${kind})`, () => {
const tag = this.el.tagName;
const cids = diff ? this.rendered.componentCIDs(diff) : null;
const { buffer: html, streams } = this.rendered.toString(cids);
return [`<${tag}>${html}</${tag}>`, streams];
});
}
componentPatch(diff, cid) {
if (isEmpty(diff))
return false;
const { buffer: html, streams } = this.rendered.componentToString(cid);
const patch = new DOMPatch(this, this.el, this.id, html, streams, cid);
const childrenAdded = this.performPatch(patch, true);
return childrenAdded;
}
getHook(el) {
return this.viewHooks[ViewHook.elementID(el)];
}
addHook(el) {
const hookElId = ViewHook.elementID(el);
if (el.getAttribute && !this.ownsElement(el)) {
return;
}
if (hookElId && !this.viewHooks[hookElId]) {
if (ViewHook.deadHook(el)) {
return;
}
const hook = dom_default.getCustomElHook(el) || logError(`no hook found for custom element: ${el.id}`);
this.viewHooks[hookElId] = hook;
hook.__attachView(this);
return hook;
} else if (hookElId || !el.getAttribute) {
return;
} else {
const hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK));
if (!hookName) {
return;
}
const hookDefinition = this.liveSocket.getHookDefinition(hookName);
if (hookDefinition) {
if (!el.id) {
logError(
`no DOM ID for hook "${hookName}". Hooks require a unique ID on each element.`,
el
);
return;
}
let hookInstance;
try {
if (typeof hookDefinition === "function" && hookDefinition.prototype instanceof ViewHook) {
hookInstance = new hookDefinition(this, el);
} else if (typeof hookDefinition === "object" && hookDefinition !== null) {
hookInstance = new ViewHook(this, el, hookDefinition);
} else {
logError(
`Invalid hook definition for "${hookName}". Expected a class extending ViewHook or an object definition.`,
el
);
return;
}
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
logError(`Failed to create hook "${hookName}": ${errorMessage}`, el);
return;
}
this.viewHooks[ViewHook.elementID(hookInstance.el)] = hookInstance;
return hookInstance;
} else if (hookName !== null) {
logError(`unknown hook found for "${hookName}"`, el);
}
}
}
destroyHook(hook) {
const hookId = ViewHook.elementID(hook.el);
hook.__destroyed();
hook.__cleanup__();
delete this.viewHooks[hookId];
}
applyPendingUpdates() {
this.pendingDiffs = this.pendingDiffs.filter(
({ diff, events }) => !this.update(diff, events, true)
);
this.eachChild((child) => child.applyPendingUpdates());
}
eachChild(callback) {
const children = this.root.children[this.id] || {};
for (const id in children) {
callback(this.getChildById(id));
}
}
onChannel(event, cb) {
this.liveSocket.onChannel(this.channel, event, (resp) => {
if (this.isJoinPending()) {
if (this.joinCount > 1) {
this.pendingJoinOps.push(() => cb(resp));
} else {
this.root.pendingJoinOps.push([this, () => cb(resp)]);
}
} else {
this.liveSocket.requestDOMUpdate(() => cb(resp));
}
});
}
bindChannel() {
this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
this.liveSocket.requestDOMUpdate(() => {
this.applyDiff(
"update",
rawDiff,
({ diff, events }) => this.update(diff, events)
);
});
});
this.onChannel(
"redirect",
({ to, flash }) => this.onRedirect({ to, flash })
);
this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
this.onChannel("live_redirect", (redir) => this.onLiveRedirect(redir));
this.channel.onError((reason) => this.onError(reason));
this.channel.onClose((reason) => this.onClose(reason));
}
destroyAllChildren() {
this.eachChild((child) => child.destroy());
}
onLiveRedirect(redir) {
const { to, kind, flash } = redir;
const url = this.expandURL(to);
const e = new CustomEvent("phx:server-navigate", {
detail: { to, kind, flash }
});
this.liveSocket.historyRedirect(e, url, kind, flash);
}
onLivePatch(redir) {
const { to, kind } = redir;
this.href = this.expandURL(to);
this.liveSocket.historyPatch(to, kind);
}
expandURL(to) {
return to.startsWith("/") ? `${window.location.protocol}//${window.location.host}${to}` : to;
}
/**
* @param {{to: string, flash?: string, reloadToken?: string}} redirect
*/
onRedirect({ to, flash, reloadToken }) {
this.liveSocket.redirect(to, flash, reloadToken);
}
isDestroyed() {
return this.destroyed;
}
joinDead() {
this.isDead = true;
}
joinPush() {
this.joinPush = this.joinPush || this.channel.join();
return this.joinPush;
}
join(callback) {
this.showLoader(this.liveSocket.loaderTimeout);
this.bindChannel();
if (this.isMain()) {
this.stopCallback = this.liveSocket.withPageLoading({
to: this.href,
kind: "initial"
});
}
this.joinCallback = (onDone) => {
onDone = onDone || function() {
};
callback ? callback(this.joinCount, onDone) : onDone();
};
this.wrapPush(() => this.channel.join(), {
ok: (resp) => this.liveSocket.requestDOMUpdate(() => this.onJoin(resp)),
error: (error) => this.onJoinError(error),
timeout: () => this.onJoinError({ reason: "timeout" })
});
}
onJoinError(resp) {
if (resp.reason === "reload") {
this.log("error", () => [
`failed mount with ${resp.status}. Falling back to page reload`,
resp
]);
this.onRedirect({
to: this.liveSocket.main.href,
reloadToken: resp.token
});
return;
} else if (resp.reason === "unauthorized" || resp.reason === "stale") {
this.log("error", () => [
"unauthorized live_redirect. Falling back to page request",
resp
]);
this.onRedirect({ to: this.liveSocket.main.href, flash: this.flash });
return;
}
if (resp.redirect || resp.live_redirect) {
this.joinPending = false;
this.channel.leave();
}
if (resp.redirect) {
return this.onRedirect(resp.redirect);
}
if (resp.live_redirect) {
return this.onLiveRedirect(resp.live_redirect);
}
this.log("error", () => ["unable to join", resp]);
if (this.isMain()) {
this.displayError(
[PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS],
{ unstructuredError: resp, errorKind: "server" }
);
if (this.liveSocket.isConnected()) {
this.liveSocket.reloadWithJitter(this);
}
} else {
if (this.joinAttempts >= MAX_CHILD_JOIN_ATTEMPTS) {
this.root.displayError(
[PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS],
{ unstructuredError: resp, errorKind: "server" }
);
this.log("error", () => [
`giving up trying to mount after ${MAX_CHILD_JOIN_ATTEMPTS} tries`,
resp
]);
this.destroy();
}
const trueChildEl = dom_default.byId(this.el.id);
if (trueChildEl) {
dom_default.mergeAttrs(trueChildEl, this.el);
this.displayError(
[PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS],
{ unstructuredError: resp, errorKind: "server" }
);
this.el = trueChildEl;
} else {
this.destroy();
}
}
}
onClose(reason) {
if (this.isDestroyed()) {
return;
}
if (this.isMain() && this.liveSocket.hasPendingLink() && reason !== "leave") {
return this.liveSocket.reloadWithJitter(this);
}
this.destroyAllChildren();
this.liveSocket.dropActiveElement(this);
if (this.liveSocket.isUnloaded()) {
this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT);
}
}
onError(reason) {
this.onClose(reason);
if (this.liveSocket.isConnected()) {
this.log("error", () => ["view crashed", reason]);
}
if (!this.liveSocket.isUnloaded()) {
if (this.liveSocket.isConnected()) {
this.displayError(
[PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS],
{ unstructuredError: reason, errorKind: "server" }
);
} else {
this.displayError(
[PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_CLIENT_ERROR_CLASS],
{ unstructuredError: reason, errorKind: "client" }
);
}
}
}
displayError(classes, details = {}) {
if (this.isMain()) {
dom_default.dispatchEvent(window, "phx:page-loading-start", {
detail: { to: this.href, kind: "error", ...details }
});
}
this.showLoader();
this.setContainerClasses(...classes);
this.delayedDisconnected();
}
delayedDisconnected() {
this.disconnectedTimer = setTimeout(() => {
this.execAll(this.binding("disconnected"));
}, this.liveSocket.disconnectedTimeout);
}
wrapPush(callerPush, receives) {
const latency = this.liveSocket.getLatencySim();
const withLatency = latency ? (cb) => setTimeout(() => !this.isDestroyed() && cb(), latency) : (cb) => !this.isDestroyed() && cb();
withLatency(() => {
callerPush().receive(
"ok",
(resp) => withLatency(() => receives.ok && receives.ok(resp))
).receive(
"error",
(reason) => withLatency(() => receives.error && receives.error(reason))
).receive(
"timeout",
() => withLatency(() => receives.timeout && receives.timeout())
);
});
}
pushWithReply(refGenerator, event, payload) {
if (!this.isConnected()) {
return Promise.reject(new Error("no connection"));
}
const [ref, [el], opts] = refGenerator ? refGenerator({ payload }) : [null, [], {}];
const oldJoinCount = this.joinCount;
let onLoadingDone = function() {
};
if (opts.page_loading) {
onLoadingDone = this.liveSocket.withPageLoading({
kind: "element",
target: el
});
}
if (typeof payload.cid !== "number") {
delete payload.cid;
}
return new Promise((resolve, reject) => {
this.wrapPush(() => this.channel.push(event, payload, PUSH_TIMEOUT), {
ok: (resp) => {
if (ref !== null) {
this.lastAckRef = ref;
}
const finish = (hookReply) => {
if (resp.redirect) {
this.onRedirect(resp.redirect);
}
if (resp.live_patch) {
this.onLivePatch(resp.live_patch);
}
if (resp.live_redirect) {
this.onLiveRedirect(resp.live_redirect);
}
onLoadingDone();
resolve({ resp, reply: hookReply, ref });
};
if (resp.diff) {
this.liveSocket.requestDOMUpdate(() => {
this.applyDiff("update", resp.diff, ({ diff, reply, events }) => {
if (ref !== null) {
this.undoRefs(ref, payload.event);
}
this.update(diff, events);
finish(reply);
});
});
} else {
if (ref !== null) {
this.undoRefs(ref, payload.event);
}
finish(null);
}
},
error: (reason) => reject(new Error(`failed with reason: ${JSON.stringify(reason)}`)),
timeout: () => {
reject(new Error("timeout"));
if (this.joinCount === oldJoinCount) {
this.liveSocket.reloadWithJitter(this, () => {
this.log("timeout", () => [
"received timeout while communicating with server. Falling back to hard refresh for recovery"
]);
});
}
}
});
});
}
undoRefs(ref, phxEvent, onlyEls) {
if (!this.isConnected()) {
return;
}
const selector = `[${PHX_REF_SRC}="${this.refSrc()}"]`;
if (onlyEls) {
onlyEls = new Set(onlyEls);
dom_default.all(document, selector, (parent) => {
if (onlyEls && !onlyEls.has(parent)) {
return;
}
dom_default.all(
parent,
selector,
(child) => this.undoElRef(child, ref, phxEvent)
);
this.undoElRef(parent, ref, phxEvent);
});
} else {
dom_default.all(document, selector, (el) => this.undoElRef(el, ref, phxEvent));
}
}
undoElRef(el, ref, phxEvent) {
const elRef = new ElementRef(el);
elRef.maybeUndo(ref, phxEvent, (clonedTree) => {
const patch = new DOMPatch(this, el, this.id, clonedTree, [], null, {
undoRef: ref
});
const phxChildrenAdded = this.performPatch(patch, true);
dom_default.all(
el,
`[${PHX_REF_SRC}="${this.refSrc()}"]`,
(child) => this.undoElRef(child, ref, phxEvent)
);
if (phxChildrenAdded) {
this.joinNewChildren();
}
});
}
refSrc() {
return this.el.id;
}
putRef(elements, phxEvent, eventType, opts = {}) {
const newRef = this.ref++;
const disableWith = this.binding(PHX_DISABLE_WITH);
if (opts.loading) {
const loadingEls = dom_default.all(document, opts.loading).map((el) => {
return { el, lock: true, loading: true };
});
elements = elements.concat(loadingEls);
}
for (const { el, lock, loading } of elements) {
if (!lock && !loading) {
throw new Error("putRef requires lock or loading");
}
el.setAttribute(PHX_REF_SRC, this.refSrc());
if (loading) {
el.setAttribute(PHX_REF_LOADING, newRef);
}
if (lock) {
el.setAttribute(PHX_REF_LOCK, newRef);
}
if (!loading || opts.submitter && !(el === opts.submitter || el === opts.form)) {
continue;
}
const lockCompletePromise = new Promise((resolve) => {
el.addEventListener(`phx:undo-lock:${newRef}`, () => resolve(detail), {
once: true
});
});
const loadingCompletePromise = new Promise((resolve) => {
el.addEventListener(
`phx:undo-loading:${newRef}`,
() => resolve(detail),
{ once: true }
);
});
el.classList.add(`phx-${eventType}-loading`);
const disableText = el.getAttribute(disableWith);
if (disableText !== null) {
if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.textContent);
}
if (disableText !== "") {
el.textContent = disableText;
}
el.setAttribute(
PHX_DISABLED,
el.getAttribute(PHX_DISABLED) || el.disabled
);
el.setAttribute("disabled", "");
}
const detail = {
event: phxEvent,
eventType,
ref: newRef,
isLoading: loading,
isLocked: lock,
lockElements: elements.filter(({ lock: lock2 }) => lock2).map(({ el: el2 }) => el2),
loadingElements: elements.filter(({ loading: loading2 }) => loading2).map(({ el: el2 }) => el2),
unlock: (els) => {
els = Array.isArray(els) ? els : [els];
this.undoRefs(newRef, phxEvent, els);
},
lockComplete: lockCompletePromise,
loadingComplete: loadingCompletePromise,
lock: (lockEl) => {
return new Promise((resolve) => {
if (this.isAcked(newRef)) {
return resolve(detail);
}
lockEl.setAttribute(PHX_REF_LOCK, newRef);
lockEl.setAttribute(PHX_REF_SRC, this.refSrc());
lockEl.addEventListener(
`phx:lock-stop:${newRef}`,
() => resolve(detail),
{ once: true }
);
});
}
};
if (opts.payload) {
detail["payload"] = opts.payload;
}
if (opts.target) {
detail["target"] = opts.target;
}
if (opts.originalEvent) {
detail["originalEvent"] = opts.originalEvent;
}
el.dispatchEvent(
new CustomEvent("phx:push", {
detail,
bubbles: true,
cancelable: false
})
);
if (phxEvent) {
el.dispatchEvent(
new CustomEvent(`phx:push:${phxEvent}`, {
detail,
bubbles: true,
cancelable: false
})
);
}
}
return [newRef, elements.map(({ el }) => el), opts];
}
isAcked(ref) {
return this.lastAckRef !== null && this.lastAckRef >= ref;
}
componentID(el) {
const cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
return cid ? parseInt(cid) : null;
}
targetComponentID(target, targetCtx, opts = {}) {
if (isCid(targetCtx)) {
return targetCtx;
}
const cidOrSelector = opts.target || target.getAttribute(this.binding("target"));
if (isCid(cidOrSelector)) {
return parseInt(cidOrSelector);
} else if (targetCtx && (cidOrSelector !== null || opts.target)) {
return this.closestComponentID(targetCtx);
} else {
return null;
}
}
closestComponentID(targetCtx) {
if (isCid(targetCtx)) {
return targetCtx;
} else if (targetCtx) {
return maybe(
// We either use the closest data-phx-component binding, or -
// in case of portals - continue with the portal source.
// This is necessary if teleporting an element outside of its LiveComponent.
targetCtx.closest(`[${PHX_COMPONENT}],[${PHX_TELEPORTED_SRC}]`),
(el) => {
if (el.hasAttribute(PHX_COMPONENT)) {
return this.ownsElement(el) && this.componentID(el);
}
if (el.hasAttribute(PHX_TELEPORTED_SRC)) {
const portalParent = dom_default.byId(el.getAttribute(PHX_TELEPORTED_SRC));
return this.closestComponentID(portalParent);
}
}
);
} else {
return null;
}
}
pushHookEvent(el, targetCtx, event, payload) {
if (!this.isConnected()) {
this.log("hook", () => [
"unable to push hook event. LiveView not connected",
event,
payload
]);
return Promise.reject(
new Error("unable to push hook event. LiveView not connected")
);
}
const refGenerator = () => this.putRef([{ el, loading: true, lock: true }], event, "hook", {
payload,
target: targetCtx
});
return this.pushWithReply(refGenerator, "event", {
type: "hook",
event,
value: payload,
cid: this.closestComponentID(targetCtx)
}).then(({ resp: _resp, reply, ref }) => ({ reply, ref }));
}
extractMeta(el, meta, value) {
const prefix = this.binding("value-");
for (let i = 0; i < el.attributes.length; i++) {
if (!meta) {
meta = {};
}
const name = el.attributes[i].name;
if (name.startsWith(prefix)) {
meta[name.replace(prefix, "")] = el.getAttribute(name);
}
}
if (el.value !== void 0 && !(el instanceof HTMLFormElement)) {
if (!meta) {
meta = {};
}
meta.value = el.value;
if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
delete meta.value;
}
}
if (value) {
if (!meta) {
meta = {};
}
for (const key in value) {
meta[key] = value[key];
}
}
return meta;
}
pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}, onReply) {
this.pushWithReply(
(maybePayload) => this.putRef([{ el, loading: true, lock: true }], phxEvent, type, {
...opts,
payload: maybePayload?.payload
}),
"event",
{
type,
event: phxEvent,
value: this.extractMeta(el, meta, opts.value),
cid: this.targetComponentID(el, targetCtx, opts)
}
).then(({ reply }) => onReply && onReply(reply)).catch((error) => logError("Failed to push event", error));
}
pushFileProgress(fileEl, entryRef, progress, onReply = function() {
}) {
this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {
view.pushWithReply(null, "progress", {
event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),
ref: fileEl.getAttribute(PHX_UPLOAD_REF),
entry_ref: entryRef,
progress,
cid: view.targetComponentID(fileEl.form, targetCtx)
}).then(() => onReply()).catch((error) => logError("Failed to push file progress", error));
});
}
pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
if (!inputEl.form) {
throw new Error("form events require the input to be inside a form");
}
let uploads;
const cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx, opts);
const refGenerator = (maybePayload) => {
return this.putRef(
[
{ el: inputEl, loading: true, lock: true },
{ el: inputEl.form, loading: true, lock: true }
],
phxEvent,
"change",
{ ...opts, payload: maybePayload?.payload }
);
};
let formData;
const meta = this.extractMeta(inputEl.form, {}, opts.value);
const serializeOpts = {};
if (inputEl instanceof HTMLButtonElement) {
serializeOpts.submitter = inputEl;
}
if (inputEl.getAttribute(this.binding("change"))) {
formData = serializeForm(inputEl.form, serializeOpts, [inputEl.name]);
} else {
formData = serializeForm(inputEl.form, serializeOpts);
}
if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
}
uploads = LiveUploader.serializeUploads(inputEl);
const event = {
type: "form",
event: phxEvent,
value: formData,
meta: {
// no target was implicitly sent as "undefined" in LV <= 1.0.5, therefore
// we have to keep it. In 1.0.6 we switched from passing meta as URL encoded data
// to passing it directly in the event, but the JSON encode would drop keys with
// undefined values.
_target: opts._target || "undefined",
...meta
},
uploads,
cid
};
this.pushWithReply(refGenerator, "event", event).then(({ resp }) => {
if (dom_default.isUploadInput(inputEl) && dom_default.isAutoUpload(inputEl)) {
ElementRef.onUnlock(inputEl, () => {
if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
const [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 {
callback && callback(resp);
}
}).catch((error) => logError("Failed to push input event", error));
}
triggerAwaitingSubmit(formEl, phxEvent) {
const awaitingSubmit = this.getScheduledSubmit(formEl);
if (awaitingSubmit) {
const [_el, _ref, _opts, callback] = awaitingSubmit;
this.cancelSubmit(formEl, phxEvent);
callback();
}
}
getScheduledSubmit(formEl) {
return this.formSubmits.find(
([el, _ref, _opts, _callback]) => el.isSameNode(formEl)
);
}
scheduleSubmit(formEl, ref, opts, callback) {
if (this.getScheduledSubmit(formEl)) {
return true;
}
this.formSubmits.push([formEl, ref, opts, callback]);
}
cancelSubmit(formEl, phxEvent) {
this.formSubmits = this.formSubmits.filter(
([el, ref, _opts, _callback]) => {
if (el.isSameNode(formEl)) {
this.undoRefs(ref, phxEvent);
return false;
} else {
return true;
}
}
);
}
disableForm(formEl, phxEvent, opts = {}) {
const filterIgnored = (el) => {
const userIgnored = closestPhxBinding(
el,
`${this.binding(PHX_UPDATE)}=ignore`,
el.form
);
return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
};
const filterDisables = (el) => {
return el.hasAttribute(this.binding(PHX_DISABLE_WITH));
};
const filterButton = (el) => el.tagName == "BUTTON";
const filterInput = (el) => ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
const formElements = Array.from(formEl.elements);
const disables = formElements.filter(filterDisables);
const buttons = formElements.filter(filterButton).filter(filterIgnored);
const inputs = formElements.filter(filterInput).filter(filterIgnored);
buttons.forEach((button) => {
button.setAttribute(PHX_DISABLED, button.disabled);
button.disabled = true;
});
inputs.forEach((input) => {
input.setAttribute(PHX_READONLY, input.readOnly);
input.readOnly = true;
if (input.files) {
input.setAttribute(PHX_DISABLED, input.disabled);
input.disabled = true;
}
});
const formEls = disables.concat(buttons).concat(inputs).map((el) => {
return { el, loading: true, lock: true };
});
const els = [{ el: formEl, loading: true, lock: false }].concat(formEls).reverse();
return this.putRef(els, phxEvent, "submit", opts);
}
pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply) {
const refGenerator = (maybePayload) => this.disableForm(formEl, phxEvent, {
...opts,
form: formEl,
payload: maybePayload?.payload,
submitter
});
dom_default.putPrivate(formEl, "submitter", submitter);
const cid = this.targetComponentID(formEl, targetCtx);
if (LiveUploader.hasUploadsInProgress(formEl)) {
const [ref, _els] = refGenerator();
const push = () => this.pushFormSubmit(
formEl,
targetCtx,
phxEvent,
submitter,
opts,
onReply
);
return this.scheduleSubmit(formEl, ref, opts, push);
} else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
const [ref, els] = refGenerator();
const proxyRefGen = () => [ref, els, opts];
this.uploadFiles(formEl, phxEvent, targetCtx, ref, cid, (_uploads) => {
if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
return this.undoRefs(ref, phxEvent);
}
const meta = this.extractMeta(formEl, {}, opts.value);
const formData = serializeForm(formEl, { submitter });
this.pushWithReply(proxyRefGen, "event", {
type: "form",
event: phxEvent,
value: formData,
meta,
cid
}).then(({ resp }) => onReply(resp)).catch((error) => logError("Failed to push form submit", error));
});
} else if (!(formEl.hasAttribute(PHX_REF_SRC) && formEl.classList.contains("phx-submit-loading"))) {
const meta = this.extractMeta(formEl, {}, opts.value);
const formData = serializeForm(formEl, { submitter });
this.pushWithReply(refGenerator, "event", {
type: "form",
event: phxEvent,
value: formData,
meta,
cid
}).then(({ resp }) => onReply(resp)).catch((error) => logError("Failed to push form submit", error));
}
}
uploadFiles(formEl, phxEvent, targetCtx, ref, cid, onComplete) {
const joinCountAtUpload = this.joinCount;
const inputEls = LiveUploader.activeFileInputs(formEl);
let numFileInputsInProgress = inputEls.length;
inputEls.forEach((inputEl) => {
const uploader = new LiveUploader(inputEl, this, () => {
numFileInputsInProgress--;
if (numFileInputsInProgress === 0) {
onComplete();
}
});
const entries = uploader.entries().map((entry) => entry.toPreflightPayload());
if (entries.length === 0) {
numFileInputsInProgress--;
return;
}
const payload = {
ref: inputEl.getAttribute(PHX_UPLOAD_REF),
entries,
cid: this.targetComponentID(inputEl.form, targetCtx)
};
this.log("upload", () => ["sending preflight request", payload]);
this.pushWithReply(null, "allow_upload", payload).then(({ resp }) => {
this.log("upload", () => ["got preflight response", resp]);
uploader.entries().forEach((entry) => {
if (resp.entries && !resp.entries[entry.ref]) {
this.handleFailedEntryPreflight(
entry.ref,
"failed preflight",
uploader
);
}
});
if (resp.error || Object.keys(resp.entries).length === 0) {
this.undoRefs(ref, phxEvent);
const errors = resp.error || [];
errors.map(([entry_ref, reason]) => {
this.handleFailedEntryPreflight(entry_ref, reason, uploader);
});
} else {
const onError = (callback) => {
this.channel.onError(() => {
if (this.joinCount === joinCountAtUpload) {
callback();
}
});
};
uploader.initAdapterUpload(resp, onError, this.liveSocket);
}
}).catch((error) => logError("Failed to push upload", error));
});
}
handleFailedEntryPreflight(uploadRef, reason, uploader) {
if (uploader.isAutoUpload()) {
const entry = uploader.entries().find((entry2) => entry2.ref === uploadRef.toString());
if (entry) {
entry.cancel();
}
} else {
uploader.entries().map((entry) => entry.cancel());
}
this.log("upload", () => [`error for entry ${uploadRef}`, reason]);
}
dispatchUploads(targetCtx, name, filesOrBlobs) {
const targetElement = this.targetCtxElement(targetCtx) || this.el;
const inputs = dom_default.findUploadInputs(targetElement).filter(
(el) => el.name === name
);
if (inputs.length === 0) {
logError(`no live file inputs found matching the name "${name}"`);
} else if (inputs.length > 1) {
logError(`duplicate live file inputs found matching the name "${name}"`);
} else {
dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {
detail: { files: filesOrBlobs }
});
}
}
targetCtxElement(targetCtx) {
if (isCid(targetCtx)) {
const [target] = dom_default.findComponentNodeList(this.id, targetCtx);
return target;
} else if (targetCtx) {
return targetCtx;
} else {
return null;
}
}
pushFormRecovery(oldForm, newForm, templateDom, callback) {
const phxChange = this.binding("change");
const phxTarget = newForm.getAttribute(this.binding("target")) || newForm;
const phxEvent = newForm.getAttribute(this.binding(PHX_AUTO_RECOVER)) || newForm.getAttribute(this.binding("change"));
const inputs = Array.from(oldForm.elements).filter(
(el) => dom_default.isFormInput(el) && el.name && !el.hasAttribute(phxChange)
);
if (inputs.length === 0) {
callback();
return;
}
inputs.forEach(
(input2) => input2.hasAttribute(PHX_UPLOAD_REF) && LiveUploader.clearFiles(input2)
);
const input = inputs.find((el) => el.type !== "hidden") || inputs[0];
let pending = 0;
this.withinTargets(
phxTarget,
(targetView, targetCtx) => {
const cid = this.targetComponentID(newForm, targetCtx);
pending++;
let e = new CustomEvent("phx:form-recovery", {
detail: { sourceElement: oldForm }
});
js_default.exec(e, "change", phxEvent, this, input, [
"push",
{
_target: input.name,
targetView,
targetCtx,
newCid: cid,
callback: () => {
pending--;
if (pending === 0) {
callback();
}
}
}
]);
},
templateDom
);
}
pushLinkPatch(e, href, targetEl, callback) {
const linkRef = this.liveSocket.setPendingLink(href);
const loading = e.isTrusted && e.type !== "popstate";
const refGen = targetEl ? () => this.putRef(
[{ el: targetEl, loading, lock: true }],
null,
"click"
) : null;
const fallback = () => this.liveSocket.redirect(window.location.href);
const url = href.startsWith("/") ? `${location.protocol}//${location.host}${href}` : href;
this.pushWithReply(refGen, "live_patch", { url }).then(
({ resp }) => {
this.liveSocket.requestDOMUpdate(() => {
if (resp.link_redirect) {
this.liveSocket.replaceMain(href, null, callback, linkRef);
} else if (resp.redirect) {
return;
} else {
if (this.liveSocket.commitPendingLink(linkRef)) {
this.href = href;
}
this.applyPendingUpdates();
callback && callback(linkRef);
}
});
},
({ error: _error, timeout: _timeout }) => fallback()
);
}
getFormsForRecovery() {
if (this.joinCount === 0) {
return {};
}
const phxChange = this.binding("change");
return dom_default.all(
document,
`#${CSS.escape(this.id)} form[${phxChange}], [${PHX_TELEPORTED_REF}="${CSS.escape(this.id)}"] form[${phxChange}]`
).filter((form) => form.id).filter((form) => form.elements.length > 0).filter(
(form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore"
).map((form) => {
const clonedForm = form.cloneNode(true);
morphdom_esm_default(clonedForm, form, {
onBeforeElUpdated: (fromEl, toEl) => {
dom_default.copyPrivates(fromEl, toEl);
if (fromEl.getAttribute("form") === form.id) {
fromEl.parentNode.removeChild(fromEl);
return false;
}
return true;
}
});
const externalElements = document.querySelectorAll(
`[form="${CSS.escape(form.id)}"]`
);
Array.from(externalElements).forEach((el) => {
const clonedEl = (
/** @type {HTMLElement} */
el.cloneNode(true)
);
morphdom_esm_default(clonedEl, el);
dom_default.copyPrivates(clonedEl, el);
clonedEl.removeAttribute("form");
clonedForm.appendChild(clonedEl);
});
return clonedForm;
}).reduce((acc, form) => {
acc[form.id] = form;
return acc;
}, {});
}
maybePushComponentsDestroyed(destroyedCIDs) {
let willDestroyCIDs = destroyedCIDs.filter((cid) => {
return dom_default.findComponentNodeList(this.id, cid).length === 0;
});
const onError = (error) => {
if (!this.isDestroyed()) {
logError("Failed to push components destroyed", error);
}
};
if (willDestroyCIDs.length > 0) {
willDestroyCIDs.forEach((cid) => this.rendered.resetRender(cid));
this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs }).then(() => {
this.liveSocket.requestDOMUpdate(() => {
let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
return dom_default.findComponentNodeList(this.id, cid).length === 0;
});
if (completelyDestroyCIDs.length > 0) {
this.pushWithReply(null, "cids_destroyed", {
cids: completelyDestroyCIDs
}).then(({ resp }) => {
this.rendered.pruneCIDs(resp.cids);
}).catch(onError);
}
});
}).catch(onError);
}
}
ownsElement(el) {
let parentViewEl = dom_default.closestViewEl(el);
return el.getAttribute(PHX_PARENT_ID) === this.id || parentViewEl && parentViewEl.id === this.id || !parentViewEl && this.isDead;
}
submitForm(form, targetCtx, phxEvent, submitter, opts = {}) {
dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
const inputs = Array.from(form.elements);
inputs.forEach((input) => dom_default.putPrivate(input, PHX_HAS_SUBMITTED, true));
this.liveSocket.blurActiveElement(this);
this.pushFormSubmit(form, targetCtx, phxEvent, submitter, opts, () => {
this.liveSocket.restorePreviouslyActiveFocus();
});
}
binding(kind) {
return this.liveSocket.binding(kind);
}
// phx-portal
pushPortalElementId(id) {
this.portalElementIds.add(id);
}
dropPortalElementId(id) {
this.portalElementIds.delete(id);
}
destroyPortalElements() {
if (!this.liveSocket.unloaded) {
this.portalElementIds.forEach((id) => {
const el = document.getElementById(id);
if (el) {
el.remove();
}
});
}
}
};
var LiveSocket = class {
constructor(url, phxSocket, opts = {}) {
this.unloaded = false;
if (!phxSocket || phxSocket.constructor.name === "Object") {
throw new Error(`
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
`);
}
this.socket = new phxSocket(url, opts);
this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX;
this.opts = opts;
this.params = closure2(opts.params || {});
this.viewLogger = opts.viewLogger;
this.metadataCallbacks = opts.metadata || {};
this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {});
this.prevActive = null;
this.silenced = false;
this.main = null;
this.outgoingMainEl = null;
this.clickStartedAtTarget = null;
this.linkRef = 1;
this.roots = {};
this.href = window.location.href;
this.pendingLink = null;
this.currentLocation = clone(window.location);
this.hooks = opts.hooks || {};
this.uploaders = opts.uploaders || {};
this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT;
this.disconnectedTimeout = opts.disconnectedTimeout || DISCONNECTED_TIMEOUT;
this.reloadWithJitterTimer = null;
this.maxReloads = opts.maxReloads || MAX_RELOADS;
this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN;
this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX;
this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER;
this.localStorage = opts.localStorage || window.localStorage;
this.sessionStorage = opts.sessionStorage || window.sessionStorage;
this.boundTopLevelEvents = false;
this.boundEventNames = /* @__PURE__ */ new Set();
this.blockPhxChangeWhileComposing = opts.blockPhxChangeWhileComposing || false;
this.serverCloseRef = null;
this.domCallbacks = Object.assign(
{
jsQuerySelectorAll: null,
onPatchStart: closure2(),
onPatchEnd: closure2(),
onNodeAdded: closure2(),
onBeforeElUpdated: closure2()
},
opts.dom || {}
);
this.transitions = new TransitionSet();
this.currentHistoryPosition = parseInt(this.sessionStorage.getItem(PHX_LV_HISTORY_POSITION)) || 0;
window.addEventListener("pagehide", (_e) => {
this.unloaded = true;
});
this.socket.onOpen(() => {
if (this.isUnloaded()) {
window.location.reload();
}
});
}
// public
version() {
return "1.1.28";
}
isProfileEnabled() {
return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
}
isDebugEnabled() {
return this.sessionStorage.getItem(PHX_LV_DEBUG) === "true";
}
isDebugDisabled() {
return this.sessionStorage.getItem(PHX_LV_DEBUG) === "false";
}
enableDebug() {
this.sessionStorage.setItem(PHX_LV_DEBUG, "true");
}
enableProfiling() {
this.sessionStorage.setItem(PHX_LV_PROFILE, "true");
}
disableDebug() {
this.sessionStorage.setItem(PHX_LV_DEBUG, "false");
}
disableProfiling() {
this.sessionStorage.removeItem(PHX_LV_PROFILE);
}
enableLatencySim(upperBoundMs) {
this.enableDebug();
console.log(
"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"
);
this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs);
}
disableLatencySim() {
this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM);
}
getLatencySim() {
const str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM);
return str ? parseInt(str) : null;
}
getSocket() {
return this.socket;
}
connect() {
if (window.location.hostname === "localhost" && !this.isDebugDisabled()) {
this.enableDebug();
}
const doConnect = () => {
this.resetReloadStatus();
if (this.joinRootViews()) {
this.bindTopLevelEvents();
this.socket.connect();
} else if (this.main) {
this.socket.connect();
} else {
this.bindTopLevelEvents({ dead: true });
}
this.joinDeadView();
};
if (["complete", "loaded", "interactive"].indexOf(document.readyState) >= 0) {
doConnect();
} else {
document.addEventListener("DOMContentLoaded", () => doConnect());
}
}
disconnect(callback) {
clearTimeout(this.reloadWithJitterTimer);
if (this.serverCloseRef) {
this.socket.off(this.serverCloseRef);
this.serverCloseRef = null;
}
this.socket.disconnect(callback);
}
replaceTransport(transport) {
clearTimeout(this.reloadWithJitterTimer);
this.socket.replaceTransport(transport);
this.connect();
}
/**
* @param {HTMLElement} el
* @param {string} encodedJS
* @param {string | null} [eventType]
*/
execJS(el, encodedJS, eventType = null) {
const e = new CustomEvent("phx:exec", { detail: { sourceElement: el } });
this.owner(el, (view) => js_default.exec(e, eventType, encodedJS, view, el));
}
/**
* Returns an object with methods to manipulate the DOM and execute JavaScript.
* The applied changes integrate with server DOM patching.
*
* @returns {import("./js_commands").LiveSocketJSCommands}
*/
js() {
return js_commands_default(this, "js");
}
// private
unload() {
if (this.unloaded) {
return;
}
if (this.main && this.isConnected()) {
this.log(this.main, "socket", () => ["disconnect for page nav"]);
}
this.unloaded = true;
this.destroyAllViews();
this.disconnect();
}
triggerDOM(kind, args) {
this.domCallbacks[kind](...args);
}
time(name, func) {
if (!this.isProfileEnabled() || !console.time) {
return func();
}
console.time(name);
const result = func();
console.timeEnd(name);
return result;
}
log(view, kind, msgCallback) {
if (this.viewLogger) {
const [msg, obj] = msgCallback();
this.viewLogger(view, kind, msg, obj);
} else if (this.isDebugEnabled()) {
const [msg, obj] = msgCallback();
debug(view, kind, msg, obj);
}
}
requestDOMUpdate(callback) {
this.transitions.after(callback);
}
asyncTransition(promise) {
this.transitions.addAsyncTransition(promise);
}
transition(time, onStart, onDone = function() {
}) {
this.transitions.addTransition(time, onStart, onDone);
}
onChannel(channel, event, cb) {
channel.on(event, (data) => {
const latency = this.getLatencySim();
if (!latency) {
cb(data);
} else {
setTimeout(() => cb(data), latency);
}
});
}
reloadWithJitter(view, log) {
clearTimeout(this.reloadWithJitterTimer);
this.disconnect();
const minMs = this.reloadJitterMin;
const maxMs = this.reloadJitterMax;
let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
const tries = browser_default.updateLocal(
this.localStorage,
window.location.pathname,
CONSECUTIVE_RELOADS,
0,
(count) => count + 1
);
if (tries >= this.maxReloads) {
afterMs = this.failsafeJitter;
}
this.reloadWithJitterTimer = setTimeout(() => {
if (view.isDestroyed() || view.isConnected()) {
return;
}
view.destroy();
log ? log() : this.log(view, "join", () => [
`encountered ${tries} consecutive reloads`
]);
if (tries >= this.maxReloads) {
this.log(view, "join", () => [
`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`
]);
}
if (this.hasPendingLink()) {
window.location = this.pendingLink;
} else {
window.location.reload();
}
}, afterMs);
}
getHookDefinition(name) {
if (!name) {
return;
}
return this.maybeInternalHook(name) || this.hooks[name] || this.maybeRuntimeHook(name);
}
maybeInternalHook(name) {
return name && name.startsWith("Phoenix.") && hooks_default[name.split(".")[1]];
}
maybeRuntimeHook(name) {
const runtimeHook = document.querySelector(
`script[${PHX_RUNTIME_HOOK}="${CSS.escape(name)}"]`
);
if (!runtimeHook) {
return;
}
let callbacks = window[`phx_hook_${name}`];
if (!callbacks || typeof callbacks !== "function") {
logError("a runtime hook must be a function", runtimeHook);
return;
}
const hookDefiniton = callbacks();
if (hookDefiniton && (typeof hookDefiniton === "object" || typeof hookDefiniton === "function")) {
return hookDefiniton;
}
logError(
"runtime hook must return an object with hook callbacks or an instance of ViewHook",
runtimeHook
);
}
isUnloaded() {
return this.unloaded;
}
isConnected() {
return this.socket.isConnected();
}
getBindingPrefix() {
return this.bindingPrefix;
}
binding(kind) {
return `${this.getBindingPrefix()}${kind}`;
}
channel(topic, params) {
return this.socket.channel(topic, params);
}
joinDeadView() {
const body = document.body;
if (body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)) {
const view = this.newRootView(body);
view.setHref(this.getHref());
view.joinDead();
if (!this.main) {
this.main = view;
}
window.requestAnimationFrame(() => {
view.execNewMounted();
this.maybeScroll(history.state?.scroll);
});
}
}
joinRootViews() {
let rootsFound = false;
dom_default.all(
document,
`${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`,
(rootEl) => {
if (!this.getRootById(rootEl.id)) {
const view = this.newRootView(rootEl);
if (!dom_default.isPhxSticky(rootEl)) {
view.setHref(this.getHref());
}
view.join();
if (rootEl.hasAttribute(PHX_MAIN)) {
this.main = view;
}
}
rootsFound = true;
}
);
return rootsFound;
}
redirect(to, flash, reloadToken) {
if (reloadToken) {
browser_default.setCookie(PHX_RELOAD_STATUS, reloadToken, 60);
}
this.unload();
browser_default.redirect(to, flash);
}
replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)) {
const liveReferer = this.currentLocation.href;
this.outgoingMainEl = this.outgoingMainEl || this.main.el;
const stickies = dom_default.findPhxSticky(document) || [];
const removeEls = dom_default.all(
this.outgoingMainEl,
`[${this.binding("remove")}]`
).filter((el) => !dom_default.isChildOfAny(el, stickies));
const newMainEl = dom_default.cloneNode(this.outgoingMainEl, "");
this.main.showLoader(this.loaderTimeout);
this.main.destroy();
this.main = this.newRootView(newMainEl, flash, liveReferer);
this.main.setRedirect(href);
this.transitionRemoves(removeEls);
this.main.join((joinCount, onDone) => {
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
this.requestDOMUpdate(() => {
removeEls.forEach((el) => el.remove());
stickies.forEach((el) => newMainEl.appendChild(el));
this.outgoingMainEl.replaceWith(newMainEl);
this.outgoingMainEl = null;
callback && callback(linkRef);
onDone();
});
}
});
}
transitionRemoves(elements, callback) {
const removeAttr = this.binding("remove");
const silenceEvents = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
};
elements.forEach((el) => {
for (const event of this.boundEventNames) {
el.addEventListener(event, silenceEvents, true);
}
this.execJS(el, el.getAttribute(removeAttr), "remove");
});
this.requestDOMUpdate(() => {
elements.forEach((el) => {
for (const event of this.boundEventNames) {
el.removeEventListener(event, silenceEvents, true);
}
});
callback && callback();
});
}
isPhxView(el) {
return el.getAttribute && el.getAttribute(PHX_SESSION) !== null;
}
newRootView(el, flash, liveReferer) {
const view = new View(el, this, null, flash, liveReferer);
this.roots[view.id] = view;
return view;
}
owner(childEl, callback) {
let view;
const viewEl = dom_default.closestViewEl(childEl);
if (viewEl) {
view = this.getViewByEl(viewEl);
} else {
if (!childEl.isConnected) {
return null;
}
view = this.main;
}
return view && callback ? callback(view) : view;
}
withinOwners(childEl, callback) {
this.owner(childEl, (view) => callback(view, childEl));
}
getViewByEl(el) {
const rootId = el.getAttribute(PHX_ROOT_ID);
return maybe(
this.getRootById(rootId),
(root) => root.getDescendentByEl(el)
);
}
getRootById(id) {
return this.roots[id];
}
destroyAllViews() {
for (const id in this.roots) {
this.roots[id].destroy();
delete this.roots[id];
}
this.main = null;
}
destroyViewByEl(el) {
const root = this.getRootById(el.getAttribute(PHX_ROOT_ID));
if (root && root.id === el.id) {
root.destroy();
delete this.roots[root.id];
} else if (root) {
root.destroyDescendent(el.id);
}
}
getActiveElement() {
return document.activeElement;
}
dropActiveElement(view) {
if (this.prevActive && view.ownsElement(this.prevActive)) {
this.prevActive = null;
}
}
restorePreviouslyActiveFocus() {
if (this.prevActive && this.prevActive !== document.body && this.prevActive instanceof HTMLElement) {
this.prevActive.focus();
}
}
blurActiveElement() {
this.prevActive = this.getActiveElement();
if (this.prevActive !== document.body && this.prevActive instanceof HTMLElement) {
this.prevActive.blur();
}
}
/**
* @param {{dead?: boolean}} [options={}]
*/
bindTopLevelEvents({ dead } = {}) {
if (this.boundTopLevelEvents) {
return;
}
this.boundTopLevelEvents = true;
this.serverCloseRef = this.socket.onClose((event) => {
if (event && event.code === 1e3 && this.main) {
return this.reloadWithJitter(this.main);
}
});
document.body.addEventListener("click", function() {
});
window.addEventListener(
"pageshow",
(e) => {
if (e.persisted) {
this.getSocket().disconnect();
this.withPageLoading({ to: window.location.href, kind: "redirect" });
window.location.reload();
}
},
true
);
if (!dead) {
this.bindNav();
}
this.bindClicks();
if (!dead) {
this.bindForms();
}
this.bind(
{ keyup: "keyup", keydown: "keydown" },
(e, type, view, targetEl, phxEvent, _phxTarget) => {
const matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
const pressedKey = e.key && e.key.toLowerCase();
if (matchKey && matchKey.toLowerCase() !== pressedKey) {
return;
}
const data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
js_default.exec(e, type, phxEvent, view, targetEl, ["push", { data }]);
}
);
this.bind(
{ blur: "focusout", focus: "focusin" },
(e, type, view, targetEl, phxEvent, phxTarget) => {
if (!phxTarget) {
const data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
js_default.exec(e, type, phxEvent, view, targetEl, ["push", { data }]);
}
}
);
this.bind(
{ blur: "blur", focus: "focus" },
(e, type, view, targetEl, phxEvent, phxTarget) => {
if (phxTarget === "window") {
const data = this.eventMeta(type, e, targetEl);
js_default.exec(e, type, phxEvent, view, targetEl, ["push", { data }]);
}
}
);
this.on("dragover", (e) => e.preventDefault());
this.on("dragenter", (e) => {
const dropzone = closestPhxBinding(
e.target,
this.binding(PHX_DROP_TARGET)
);
if (!dropzone || !(dropzone instanceof HTMLElement)) {
return;
}
if (eventContainsFiles(e)) {
this.js().addClass(dropzone, PHX_DROP_TARGET_ACTIVE_CLASS);
}
});
this.on("dragleave", (e) => {
const dropzone = closestPhxBinding(
e.target,
this.binding(PHX_DROP_TARGET)
);
if (!dropzone || !(dropzone instanceof HTMLElement)) {
return;
}
const rect = dropzone.getBoundingClientRect();
if (e.clientX <= rect.left || e.clientX >= rect.right || e.clientY <= rect.top || e.clientY >= rect.bottom) {
this.js().removeClass(dropzone, PHX_DROP_TARGET_ACTIVE_CLASS);
}
});
this.on("drop", (e) => {
e.preventDefault();
const dropzone = closestPhxBinding(
e.target,
this.binding(PHX_DROP_TARGET)
);
if (!dropzone || !(dropzone instanceof HTMLElement)) {
return;
}
this.js().removeClass(dropzone, PHX_DROP_TARGET_ACTIVE_CLASS);
const dropTargetId = dropzone.getAttribute(this.binding(PHX_DROP_TARGET));
const dropTarget = dropTargetId && document.getElementById(dropTargetId);
const files = Array.from(e.dataTransfer.files || []);
if (!dropTarget || !(dropTarget instanceof HTMLInputElement) || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)) {
return;
}
LiveUploader.trackFiles(dropTarget, files, e.dataTransfer);
dropTarget.dispatchEvent(new Event("input", { bubbles: true }));
});
this.on(PHX_TRACK_UPLOADS, (e) => {
const uploadTarget = e.target;
if (!dom_default.isUploadInput(uploadTarget)) {
return;
}
const files = Array.from(e.detail.files || []).filter(
(f) => f instanceof File || f instanceof Blob
);
LiveUploader.trackFiles(uploadTarget, files);
uploadTarget.dispatchEvent(new Event("input", { bubbles: true }));
});
}
eventMeta(eventName, e, targetEl) {
const callback = this.metadataCallbacks[eventName];
return callback ? callback(e, targetEl) : {};
}
setPendingLink(href) {
this.linkRef++;
this.pendingLink = href;
this.resetReloadStatus();
return this.linkRef;
}
// anytime we are navigating or connecting, drop reload cookie in case
// we issue the cookie but the next request was interrupted and the server never dropped it
resetReloadStatus() {
browser_default.deleteCookie(PHX_RELOAD_STATUS);
}
commitPendingLink(linkRef) {
if (this.linkRef !== linkRef) {
return false;
} else {
this.href = this.pendingLink;
this.pendingLink = null;
return true;
}
}
getHref() {
return this.href;
}
hasPendingLink() {
return !!this.pendingLink;
}
bind(events, callback) {
for (const event in events) {
const browserEventName = events[event];
this.on(browserEventName, (e) => {
const binding = this.binding(event);
const windowBinding = this.binding(`window-${event}`);
const targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
if (targetPhxEvent) {
this.debounce(e.target, e, browserEventName, () => {
this.withinOwners(e.target, (view) => {
callback(e, event, view, e.target, targetPhxEvent, null);
});
});
} else {
dom_default.all(document, `[${windowBinding}]`, (el) => {
const phxEvent = el.getAttribute(windowBinding);
this.debounce(el, e, browserEventName, () => {
this.withinOwners(el, (view) => {
callback(e, event, view, el, phxEvent, "window");
});
});
});
}
});
}
}
bindClicks() {
this.on("mousedown", (e) => this.clickStartedAtTarget = e.target);
this.bindClick("click", "click");
}
bindClick(eventName, bindingName) {
const click = this.binding(bindingName);
window.addEventListener(
eventName,
(e) => {
let target = null;
if (e.detail === 0)
this.clickStartedAtTarget = e.target;
const clickStartedAtTarget = this.clickStartedAtTarget || e.target;
target = closestPhxBinding(e.target, click);
this.dispatchClickAway(e, clickStartedAtTarget);
this.clickStartedAtTarget = null;
const phxEvent = target && target.getAttribute(click);
if (!phxEvent) {
if (dom_default.isNewPageClick(e, window.location)) {
this.unload();
}
return;
}
if (target.getAttribute("href") === "#") {
e.preventDefault();
}
if (target.hasAttribute(PHX_REF_SRC)) {
return;
}
this.debounce(target, e, "click", () => {
this.withinOwners(target, (view) => {
js_default.exec(e, "click", phxEvent, view, target, [
"push",
{ data: this.eventMeta("click", e, target) }
]);
});
});
},
false
);
}
dispatchClickAway(e, clickStartedAt) {
const phxClickAway = this.binding("click-away");
const portal = clickStartedAt.closest(`[${PHX_TELEPORTED_SRC}]`);
const portalStartedAt = portal && dom_default.byId(portal.getAttribute(PHX_TELEPORTED_SRC));
dom_default.all(document, `[${phxClickAway}]`, (el) => {
let startedAt = clickStartedAt;
if (portal && !portal.contains(el)) {
startedAt = portalStartedAt;
}
if (!(el.isSameNode(startedAt) || el.contains(startedAt) || // When clicking a link with custom method,
// phoenix_html triggers a click on a submit button
// of a hidden form appended to the body. For such cases
// where the clicked target is hidden, we skip click-away.
//
// Also, when we have a portal, we don't want to check the visibility
// of the portal source, as it's a <template> that is always not visible.
// Instead, check the visibility of the original click target.
!js_default.isVisible(clickStartedAt))) {
this.withinOwners(el, (view) => {
const phxEvent = el.getAttribute(phxClickAway);
if (js_default.isVisible(el) && js_default.isInViewport(el)) {
js_default.exec(e, "click", phxEvent, view, el, [
"push",
{ data: this.eventMeta("click", e, e.target) }
]);
}
});
}
});
}
bindNav() {
if (!browser_default.canPushState()) {
return;
}
if (history.scrollRestoration) {
history.scrollRestoration = "manual";
}
let scrollTimer = null;
window.addEventListener("scroll", (_e) => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
browser_default.updateCurrentState(
(state) => Object.assign(state, { scroll: window.scrollY })
);
}, 100);
});
window.addEventListener(
"popstate",
(event) => {
if (!this.registerNewLocation(window.location)) {
return;
}
const { type, backType, id, scroll, position } = event.state || {};
const href = window.location.href;
const isForward = position > this.currentHistoryPosition;
const navType = isForward ? type : backType || type;
this.currentHistoryPosition = position || 0;
this.sessionStorage.setItem(
PHX_LV_HISTORY_POSITION,
this.currentHistoryPosition.toString()
);
dom_default.dispatchEvent(window, "phx:navigate", {
detail: {
href,
patch: navType === "patch",
pop: true,
direction: isForward ? "forward" : "backward"
}
});
this.requestDOMUpdate(() => {
const callback = () => {
this.maybeScroll(scroll);
};
if (this.main.isConnected() && navType === "patch" && id === this.main.id) {
this.main.pushLinkPatch(event, href, null, callback);
} else {
this.replaceMain(href, null, callback);
}
});
},
false
);
window.addEventListener(
"click",
(e) => {
const target = closestPhxBinding(e.target, PHX_LIVE_LINK);
const type = target && target.getAttribute(PHX_LIVE_LINK);
if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
return;
}
const href = target.href instanceof SVGAnimatedString ? target.href.baseVal : target.href;
const linkState = target.getAttribute(PHX_LINK_STATE);
e.preventDefault();
e.stopImmediatePropagation();
if (this.pendingLink === href) {
return;
}
this.requestDOMUpdate(() => {
if (type === "patch") {
this.pushHistoryPatch(e, href, linkState, target);
} else if (type === "redirect") {
this.historyRedirect(e, href, linkState, null, target);
} else {
throw new Error(
`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`
);
}
const phxClick = target.getAttribute(this.binding("click"));
if (phxClick) {
this.requestDOMUpdate(() => this.execJS(target, phxClick, "click"));
}
});
},
false
);
}
maybeScroll(scroll) {
if (typeof scroll === "number") {
requestAnimationFrame(() => {
window.scrollTo(0, scroll);
});
}
}
dispatchEvent(event, payload = {}) {
dom_default.dispatchEvent(window, `phx:${event}`, { detail: payload });
}
dispatchEvents(events) {
events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
}
withPageLoading(info, callback) {
dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: info });
const done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", { detail: info });
return callback ? callback(done) : done;
}
pushHistoryPatch(e, href, linkState, targetEl) {
if (!this.isConnected() || !this.main.isMain()) {
return browser_default.redirect(href);
}
this.withPageLoading({ to: href, kind: "patch" }, (done) => {
this.main.pushLinkPatch(e, href, targetEl, (linkRef) => {
this.historyPatch(href, linkState, linkRef);
done();
});
});
}
historyPatch(href, linkState, linkRef = this.setPendingLink(href)) {
if (!this.commitPendingLink(linkRef)) {
return;
}
this.currentHistoryPosition++;
this.sessionStorage.setItem(
PHX_LV_HISTORY_POSITION,
this.currentHistoryPosition.toString()
);
browser_default.updateCurrentState((state) => ({ ...state, backType: "patch" }));
browser_default.pushState(
linkState,
{
type: "patch",
id: this.main.id,
position: this.currentHistoryPosition
},
href
);
dom_default.dispatchEvent(window, "phx:navigate", {
detail: { patch: true, href, pop: false, direction: "forward" }
});
this.registerNewLocation(window.location);
}
historyRedirect(e, href, linkState, flash, targetEl) {
const clickLoading = targetEl && e.isTrusted && e.type !== "popstate";
if (clickLoading) {
targetEl.classList.add("phx-click-loading");
}
if (!this.isConnected() || !this.main.isMain()) {
return browser_default.redirect(href, flash);
}
if (/^\/$|^\/[^\/]+.*$/.test(href)) {
const { protocol, host } = window.location;
href = `${protocol}//${host}${href}`;
}
const scroll = window.scrollY;
this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
this.replaceMain(href, flash, (linkRef) => {
if (linkRef === this.linkRef) {
this.currentHistoryPosition++;
this.sessionStorage.setItem(
PHX_LV_HISTORY_POSITION,
this.currentHistoryPosition.toString()
);
browser_default.updateCurrentState((state) => ({
...state,
backType: "redirect"
}));
browser_default.pushState(
linkState,
{
type: "redirect",
id: this.main.id,
scroll,
position: this.currentHistoryPosition
},
href
);
dom_default.dispatchEvent(window, "phx:navigate", {
detail: { href, patch: false, pop: false, direction: "forward" }
});
this.registerNewLocation(window.location);
}
if (clickLoading) {
targetEl.classList.remove("phx-click-loading");
}
done();
});
});
}
registerNewLocation(newLocation) {
const { pathname, search } = this.currentLocation;
if (pathname + search === newLocation.pathname + newLocation.search) {
return false;
} else {
this.currentLocation = clone(newLocation);
return true;
}
}
bindForms() {
let iterations = 0;
let externalFormSubmitted = false;
this.on("submit", (e) => {
const phxSubmit = e.target.getAttribute(this.binding("submit"));
const phxChange = e.target.getAttribute(this.binding("change"));
if (!externalFormSubmitted && phxChange && !phxSubmit) {
externalFormSubmitted = true;
e.preventDefault();
this.withinOwners(e.target, (view) => {
view.disableForm(e.target);
window.requestAnimationFrame(() => {
if (dom_default.isUnloadableFormSubmit(e)) {
this.unload();
}
e.target.submit();
});
});
}
});
this.on("submit", (e) => {
const phxEvent = e.target.getAttribute(this.binding("submit"));
if (!phxEvent) {
if (dom_default.isUnloadableFormSubmit(e)) {
this.unload();
}
return;
}
e.preventDefault();
e.target.disabled = true;
this.withinOwners(e.target, (view) => {
js_default.exec(e, "submit", phxEvent, view, e.target, [
"push",
{ submitter: e.submitter }
]);
});
});
for (const type of ["change", "input"]) {
this.on(type, (e) => {
if (e instanceof CustomEvent && (e.target instanceof HTMLInputElement || e.target instanceof HTMLSelectElement || e.target instanceof HTMLTextAreaElement) && e.target.form === void 0) {
if (e.detail && e.detail.dispatcher) {
throw new Error(
`dispatching a custom ${type} event is only supported on input elements inside a form`
);
}
return;
}
const phxChange = this.binding("change");
const input = e.target;
if (this.blockPhxChangeWhileComposing && e.isComposing) {
const key = `composition-listener-${type}`;
if (!dom_default.private(input, key)) {
dom_default.putPrivate(input, key, true);
input.addEventListener(
"compositionend",
() => {
input.dispatchEvent(new Event(type, { bubbles: true }));
dom_default.deletePrivate(input, key);
},
{ once: true }
);
}
return;
}
const inputEvent = input.getAttribute(phxChange);
const formEvent = input.form && input.form.getAttribute(phxChange);
const phxEvent = inputEvent || formEvent;
if (!phxEvent) {
return;
}
if (input.type === "number" && input.validity && input.validity.badInput) {
return;
}
const dispatcher = inputEvent ? input : input.form;
const currentIterations = iterations;
iterations++;
const { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
if (at === currentIterations - 1 && type === "change" && lastType === "input") {
return;
}
dom_default.putPrivate(input, "prev-iteration", {
at: currentIterations,
type
});
this.debounce(input, e, type, () => {
this.withinOwners(dispatcher, (view) => {
dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
js_default.exec(e, "change", phxEvent, view, input, [
"push",
{ _target: e.target.name, dispatcher }
]);
});
});
});
}
this.on("reset", (e) => {
const form = e.target;
dom_default.resetForm(form);
const input = Array.from(form.elements).find((el) => el.type === "reset");
if (input) {
window.requestAnimationFrame(() => {
input.dispatchEvent(
new Event("input", { bubbles: true, cancelable: false })
);
});
}
});
}
debounce(el, event, eventType, callback) {
if (eventType === "blur" || eventType === "focusout") {
return callback();
}
const phxDebounce = this.binding(PHX_DEBOUNCE);
const phxThrottle = this.binding(PHX_THROTTLE);
const defaultDebounce = this.defaults.debounce.toString();
const defaultThrottle = this.defaults.throttle.toString();
this.withinOwners(el, (view) => {
const asyncFilter = () => !view.isDestroyed() && document.body.contains(el);
dom_default.debounce(
el,
event,
phxDebounce,
defaultDebounce,
phxThrottle,
defaultThrottle,
asyncFilter,
() => {
callback();
}
);
});
}
silenceEvents(callback) {
this.silenced = true;
callback();
this.silenced = false;
}
on(event, callback) {
this.boundEventNames.add(event);
window.addEventListener(event, (e) => {
if (!this.silenced) {
callback(e);
}
});
}
jsQuerySelectorAll(sourceEl, query, defaultQuery) {
const all = this.domCallbacks.jsQuerySelectorAll;
return all ? all(sourceEl, query, defaultQuery) : defaultQuery();
}
};
var TransitionSet = class {
constructor() {
this.transitions = /* @__PURE__ */ new Set();
this.promises = /* @__PURE__ */ new Set();
this.pendingOps = [];
}
reset() {
this.transitions.forEach((timer) => {
clearTimeout(timer);
this.transitions.delete(timer);
});
this.promises.clear();
this.flushPendingOps();
}
after(callback) {
if (this.size() === 0) {
callback();
} else {
this.pushPendingOp(callback);
}
}
addTransition(time, onStart, onDone) {
onStart();
const timer = setTimeout(() => {
this.transitions.delete(timer);
onDone();
this.flushPendingOps();
}, time);
this.transitions.add(timer);
}
addAsyncTransition(promise) {
this.promises.add(promise);
promise.then(() => {
this.promises.delete(promise);
this.flushPendingOps();
});
}
pushPendingOp(op) {
this.pendingOps.push(op);
}
size() {
return this.transitions.size + this.promises.size;
}
flushPendingOps() {
if (this.size() > 0) {
return;
}
const op = this.pendingOps.shift();
if (op) {
op();
this.flushPendingOps();
}
}
};
var LiveSocket2 = LiveSocket;
// ../deps/phoenix_html/priv/static/phoenix_html.js
(function() {
var PolyfillEvent = eventConstructor();
function eventConstructor() {
if (typeof window.CustomEvent === "function") return window.CustomEvent;
function CustomEvent2(event, params) {
params = params || { bubbles: false, cancelable: false, detail: void 0 };
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent2.prototype = window.Event.prototype;
return CustomEvent2;
}
function buildHiddenInput(name, value) {
var input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = value;
return input;
}
function handleClick(element, targetModifierKey) {
var to = element.getAttribute("data-to"), method = buildHiddenInput("_method", element.getAttribute("data-method")), csrf = buildHiddenInput("_csrf_token", element.getAttribute("data-csrf")), form = document.createElement("form"), submit = document.createElement("input"), target = element.getAttribute("target");
form.method = element.getAttribute("data-method") === "get" ? "get" : "post";
form.action = to;
form.style.display = "none";
if (target) form.target = target;
else if (targetModifierKey) form.target = "_blank";
form.appendChild(csrf);
form.appendChild(method);
document.body.appendChild(form);
submit.type = "submit";
form.appendChild(submit);
submit.click();
}
window.addEventListener("click", function(e) {
var element = e.target;
if (e.defaultPrevented) return;
while (element && element.getAttribute) {
var phoenixLinkEvent = new PolyfillEvent("phoenix.link.click", {
"bubbles": true,
"cancelable": true
});
if (!element.dispatchEvent(phoenixLinkEvent)) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
if (element.getAttribute("data-method") && element.getAttribute("data-to")) {
handleClick(element, e.metaKey || e.shiftKey);
e.preventDefault();
return false;
} else {
element = element.parentNode;
}
}
}, false);
window.addEventListener("phoenix.link.click", function(e) {
var message = e.target.getAttribute("data-confirm");
if (message && !window.confirm(message)) {
e.preventDefault();
}
}, false);
})();
// js/constants.js
var SIDEBAR_STORAGE_KEY = "bds-panel-sidebar";
var ASSISTANT_STORAGE_KEY = "bds-panel-assistant-sidebar";
var UI_LANGUAGE_STORAGE_KEY = "bds-ui-language";
var WORKBENCH_SESSION_STORAGE_KEY_PREFIX = "bds-workbench-";
// js/utils/dom.js
var clamp = (value, min, max) => Math.max(min, Math.min(value, max));
var parseJsonObject = (value) => {
if (!value) {
return null;
}
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
} catch (_error) {
return null;
}
};
var setMediaThumbnailLoaded = (image, loaded) => {
const thumbnail = image?.closest(".media-thumbnail");
if (!thumbnail) {
return;
}
if (loaded) {
thumbnail.classList.add("is-loaded");
} else {
thumbnail.classList.remove("is-loaded");
}
};
var syncMediaThumbnailState = (root) => {
root.querySelectorAll(".media-thumbnail-image").forEach((image) => {
setMediaThumbnailLoaded(image, Boolean(image.complete && image.naturalWidth > 0));
});
};
// js/utils/layout.js
var shellWidth = (selector) => {
const shell = document.querySelector(selector);
if (!shell) {
return 0;
}
const width = Number.parseInt(shell.style.width || "0", 10);
return Number.isNaN(width) ? Math.round(shell.getBoundingClientRect().width) : width;
};
var setShellWidth = (selector, width) => {
const shell = document.querySelector(selector);
if (shell) {
shell.style.width = `${width}px`;
shell.classList.remove("is-hidden");
}
};
var persistWidth = (target, width) => {
const key = target === "assistant" ? ASSISTANT_STORAGE_KEY : SIDEBAR_STORAGE_KEY;
window.localStorage.setItem(key, String(width));
};
var readStoredSize = (key, fallback, min, max) => {
const raw = window.localStorage.getItem(key);
if (!raw) {
return fallback;
}
const parsed = Number.parseInt(raw, 10);
if (Number.isNaN(parsed)) {
return fallback;
}
return clamp(parsed, min, max);
};
// js/utils/shortcuts.js
var normalizeShortcutKey = (key) => String(key || "").toLowerCase();
var shortcutTargetIsEditable = (event) => {
const tag = event.target?.tagName || null;
return event.target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(tag);
};
var shortcutMatchesEvent = (shortcut, event) => {
const primary = event.metaKey || event.ctrlKey;
return normalizeShortcutKey(event.key) === normalizeShortcutKey(shortcut.key) && primary === Boolean(shortcut.primary) && event.shiftKey === Boolean(shortcut.shift) && event.altKey === Boolean(shortcut.alt);
};
var parseShortcutConfig = (value) => {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (_error) {
return [];
}
};
// js/bridges/titlebar_overlay.js
var syncTitlebarOverlayInsets = () => {
const rootStyle = document.documentElement.style;
const setInsets = (left, right) => {
rootStyle.setProperty("--bds-titlebar-overlay-left", `${left}px`);
rootStyle.setProperty("--bds-titlebar-overlay-right", `${right}px`);
};
const overlay = navigator.windowControlsOverlay;
if (!overlay) {
setInsets(0, 0);
return () => {
};
}
const updateInsets = () => {
if (!overlay.visible) {
setInsets(0, 0);
return;
}
const titlebarRect = overlay.getTitlebarAreaRect();
const viewportWidth = window.innerWidth || document.documentElement.clientWidth || titlebarRect.right;
const leftInset = Math.max(0, Math.round(titlebarRect.left));
const rightInset = Math.max(0, Math.round(viewportWidth - titlebarRect.right));
setInsets(leftInset, rightInset);
};
const onGeometryChange = () => updateInsets();
const onResize = () => updateInsets();
updateInsets();
overlay.addEventListener("geometrychange", onGeometryChange);
window.addEventListener("resize", onResize);
return () => {
overlay.removeEventListener("geometrychange", onGeometryChange);
window.removeEventListener("resize", onResize);
};
};
// js/utils/script_loader.js
var loadScript = (src) => new Promise((resolve, reject) => {
const existing = document.querySelector(`script[src="${src}"]`);
if (existing) {
if (existing.dataset.loaded === "true") {
resolve();
return;
}
existing.addEventListener("load", () => resolve(), { once: true });
existing.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)), {
once: true
});
return;
}
const script = document.createElement("script");
script.src = src;
script.async = true;
script.addEventListener(
"load",
() => {
script.dataset.loaded = "true";
resolve();
},
{ once: true }
);
script.addEventListener("error", () => reject(new Error(`Failed to load ${src}`)), {
once: true
});
document.head.appendChild(script);
});
// js/utils/color.js
var cssVar = (name, fallback) => {
const value = window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return value || fallback;
};
var parseRgbColor = (value) => {
if (!value) {
return null;
}
const hex = value.match(/^#([0-9a-f]{6})$/i);
if (hex) {
return {
r: Number.parseInt(hex[1].slice(0, 2), 16),
g: Number.parseInt(hex[1].slice(2, 4), 16),
b: Number.parseInt(hex[1].slice(4, 6), 16)
};
}
const rgb = value.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
if (!rgb) {
return null;
}
return {
r: Number.parseInt(rgb[1], 10),
g: Number.parseInt(rgb[2], 10),
b: Number.parseInt(rgb[3], 10)
};
};
var normalizeMonacoColor = (value, fallback) => {
const rgb = parseRgbColor(value);
if (!rgb) {
return fallback;
}
return `#${[rgb.r, rgb.g, rgb.b].map((channel) => clamp(channel, 0, 255).toString(16).padStart(2, "0")).join("")}`;
};
// js/monaco/theme.js
var monacoThemeSignature = null;
var ensureMonacoTheme = (monaco) => {
const background = normalizeMonacoColor(
cssVar("--vscode-editor-background", cssVar("--vscode-input-background", "#1e1e1e")),
"#1e1e1e"
);
const foreground = normalizeMonacoColor(cssVar("--vscode-editor-foreground", "#d4d4d4"), "#d4d4d4");
const lineNumber = normalizeMonacoColor(cssVar("--vscode-editorLineNumber-foreground", "#858585"), "#858585");
const activeLineNumber = normalizeMonacoColor(
cssVar("--vscode-editorLineNumber-activeForeground", foreground),
foreground
);
const selection = normalizeMonacoColor(cssVar("--vscode-editor-selectionBackground", "#264f78"), "#264f78");
const inactiveSelection = normalizeMonacoColor(
cssVar("--vscode-editor-inactiveSelectionBackground", "#3a3d41"),
"#3a3d41"
);
const cursor = normalizeMonacoColor(cssVar("--vscode-editorCursor-foreground", foreground), foreground);
const border = normalizeMonacoColor(cssVar("--vscode-panel-border", "#3c3c3c"), "#3c3c3c");
const lineHighlight = normalizeMonacoColor(
cssVar("--vscode-editor-lineHighlightBackground", background),
background
);
const signature = [background, foreground, lineNumber, activeLineNumber, selection, inactiveSelection, cursor, border].join("|");
if (signature === monacoThemeSignature) {
monaco.editor.setTheme("bds-theme");
return;
}
monaco.editor.defineTheme("bds-theme", {
base: "vs-dark",
inherit: true,
rules: [
{ token: "keyword.macro", foreground: "C586C0", fontStyle: "bold" },
{ token: "attribute.name", foreground: "9CDCFE" },
{ token: "attribute.value", foreground: "CE9178" }
],
colors: {
"editor.background": background,
"editor.foreground": foreground,
"editor.lineHighlightBackground": lineHighlight,
"editorCursor.foreground": cursor,
"editor.selectionBackground": selection,
"editor.inactiveSelectionBackground": inactiveSelection,
"editorLineNumber.foreground": lineNumber,
"editorLineNumber.activeForeground": activeLineNumber,
"editorIndentGuide.background1": border,
"editorIndentGuide.activeBackground1": foreground,
"editorWidget.border": border,
"editorGutter.background": background,
"focusBorder": border,
"input.border": border
}
});
monacoThemeSignature = signature;
monaco.editor.setTheme("bds-theme");
};
// js/monaco/languages.js
var liquidLanguageRegistered = false;
var markdownWithMacrosRegistered = false;
var registerLiquidLanguage = (monaco) => {
if (liquidLanguageRegistered) {
return;
}
monaco.languages.register({ id: "liquid" });
monaco.languages.setLanguageConfiguration("liquid", {
comments: {
blockComment: ["{% comment %}", "{% endcomment %}"]
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"]
],
autoClosingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
]
});
monaco.languages.setMonarchTokensProvider("liquid", {
defaultToken: "",
tokenizer: {
root: [
[/\{\{-?/, { token: "delimiter.output", next: "@liquidOutput" }],
[/\{%-?\s*comment\b[^%]*-?%\}/, { token: "comment.block", next: "@liquidComment" }],
[/\{%-?/, { token: "delimiter.tag", next: "@liquidTag" }],
[/<!DOCTYPE/i, "metatag"],
[/<!--/, { token: "comment", next: "@htmlComment" }],
[/(<)(script)/i, ["delimiter.html", "tag.html"], "@scriptTag"],
[/(<)(style)/i, ["delimiter.html", "tag.html"], "@styleTag"],
[/(<\/)([\w:-]+)/, ["delimiter.html", "tag.html"]],
[/(<)([\w:-]+)/, ["delimiter.html", "tag.html"], "@htmlTag"],
[/[^<{]+/, ""],
[/./, ""]
],
liquidOutput: [
[/-?\}\}/, { token: "delimiter.output", next: "@pop" }],
[/\|\s*[a-zA-Z_][\w-]*/, "keyword"],
[/\b(?:true|false|nil|blank|empty)\b/, "keyword"],
[/\b\d+(?:\.\d+)?\b/, "number"],
[/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/, "string"],
[/[a-zA-Z_][\w.-]*/, "identifier"],
[/[,:()[\]]/, "delimiter"]
],
liquidTag: [
[/-?%\}/, { token: "delimiter.tag", next: "@pop" }],
[/\b(?:assign|capture|case|comment|cycle|decrement|echo|elsif|else|endcase|endcapture|endif|endfor|endunless|endcomment|for|if|include|increment|liquid|paginate|raw|render|tablerow|unless|when)\b/, "keyword"],
[/\|\s*[a-zA-Z_][\w-]*/, "keyword"],
[/\b(?:true|false|nil|blank|empty|contains)\b/, "keyword"],
[/\b\d+(?:\.\d+)?\b/, "number"],
[/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/, "string"],
[/[><=!]=?|\.|:/, "operator"],
[/[a-zA-Z_][\w.-]*/, "identifier"],
[/[,:()[\]]/, "delimiter"]
],
liquidComment: [
[/\{%-?\s*endcomment\s*-?%\}/, { token: "comment.block", next: "@pop" }],
[/./, "comment.block"]
],
htmlComment: [
[/-->/, { token: "comment", next: "@pop" }],
[/./, "comment"]
],
htmlTag: [
[/\/>/, { token: "delimiter.html", next: "@pop" }],
[/>/, { token: "delimiter.html", next: "@pop" }],
[/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/, "attribute.value"],
[/[\w:-]+/, "attribute.name"],
[/=/, "delimiter"]
],
scriptTag: [
[/>/, { token: "delimiter.html", next: "@pop" }],
[/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/, "attribute.value"],
[/[\w:-]+/, "attribute.name"],
[/=/, "delimiter"]
],
styleTag: [
[/>/, { token: "delimiter.html", next: "@pop" }],
[/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/, "attribute.value"],
[/[\w:-]+/, "attribute.name"],
[/=/, "delimiter"]
]
}
});
liquidLanguageRegistered = true;
};
var registerMarkdownWithMacrosLanguage = (monaco) => {
if (markdownWithMacrosRegistered) {
return;
}
monaco.languages.register({ id: "markdown-with-macros" });
monaco.languages.setMonarchTokensProvider("markdown-with-macros", {
defaultToken: "",
tokenPostfix: ".md",
tokenizer: {
root: [
[/\[\[[a-zA-Z][\w-]*/, { token: "keyword.macro", next: "@macroParams" }],
[/^#{1,6}\s.*$/, "keyword.header"],
[/^\s*>+/, "string.quote"],
[/^\s*[-+*]\s/, "keyword"],
[/^\s*\d+\.\s/, "keyword"],
[/^\s*```\w*/, { token: "string.code", next: "@codeblock" }],
[/\*\*[^*]+\*\*/, "strong"],
[/\*[^*]+\*/, "emphasis"],
[/__[^_]+__/, "strong"],
[/_[^_]+_/, "emphasis"],
[/`[^`]+`/, "variable"],
[/!?\[[^\]]*\]\([^)]*\)/, "string.link"],
[/!?\[[^\]]*\]\[[^\]]*\]/, "string.link"]
],
macroParams: [
[/\]\]/, { token: "keyword.macro", next: "@root" }],
[/[a-zA-Z][\w-]*(?=\s*=)/, "attribute.name"],
[/=/, "delimiter"],
[/"[^"]*"/, "string"],
[/\s+/, "white"],
[/[^\]"=\s]+/, "attribute.value"]
],
codeblock: [
[/^\s*```\s*$/, { token: "string.code", next: "@root" }],
[/.*$/, "variable.source"]
]
}
});
markdownWithMacrosRegistered = true;
};
// js/monaco/services.js
var monacoLoaderPromise;
var monacoEditors = /* @__PURE__ */ new Map();
var loadMonaco = () => {
if (window.monaco?.editor) {
ensureMonacoTheme(window.monaco);
registerLiquidLanguage(window.monaco);
registerMarkdownWithMacrosLanguage(window.monaco);
return Promise.resolve(window.monaco);
}
if (monacoLoaderPromise) {
return monacoLoaderPromise;
}
monacoLoaderPromise = loadScript("/monaco/vs/loader.js").then(
() => new Promise((resolve, reject) => {
window.require.config({ paths: { vs: "/monaco/vs" } });
window.require(["vs/editor/editor.main"], () => {
ensureMonacoTheme(window.monaco);
registerLiquidLanguage(window.monaco);
registerMarkdownWithMacrosLanguage(window.monaco);
resolve(window.monaco);
}, reject);
})
).catch((error) => {
monacoLoaderPromise = null;
throw error;
});
return monacoLoaderPromise;
};
var registerMonacoEditor = (key, editor) => {
if (key) {
monacoEditors.set(key, editor);
}
};
var unregisterMonacoEditor = (key) => {
if (key) {
monacoEditors.delete(key);
}
};
var activeMonacoEditor = () => {
for (const editor of monacoEditors.values()) {
if (typeof editor?.hasTextFocus === "function" && editor.hasTextFocus()) {
return editor;
}
}
return null;
};
var runMonacoEditorAction = (editor, actionId, triggerId = actionId) => {
if (!editor) {
return false;
}
const action = typeof editor.getAction === "function" ? editor.getAction(actionId) : null;
if (action && typeof action.run === "function") {
action.run();
return true;
}
if (typeof editor.trigger === "function") {
editor.trigger("bds-menu", triggerId, null);
return true;
}
return false;
};
var diffModelPath = (filePath, side) => {
const normalized = String(filePath || "working-tree").replace(/^\/+/, "");
return `inmemory://model/git-diff/${side}/${normalized}`;
};
// js/bridges/document_commands.js
var applyAppZoom = (nextZoom) => {
const zoom = clamp(Math.round(nextZoom * 100) / 100, 0.5, 2);
window.__bdsAppZoom = zoom;
document.documentElement.style.zoom = String(zoom);
};
var runDocumentCommand = (command) => {
if (typeof document.execCommand !== "function") {
return false;
}
try {
return document.execCommand(command);
} catch (_error) {
return false;
}
};
// js/bridges/menu_runtime.js
var runMenuRuntimeCommand = (action) => {
const editor = activeMonacoEditor();
switch (action) {
case "undo":
return editor ? runMonacoEditorAction(editor, "undo") : runDocumentCommand("undo");
case "redo":
return editor ? runMonacoEditorAction(editor, "redo") : runDocumentCommand("redo");
case "cut":
return editor ? runMonacoEditorAction(editor, "editor.action.clipboardCutAction") : runDocumentCommand("cut");
case "copy":
return editor ? runMonacoEditorAction(editor, "editor.action.clipboardCopyAction") : runDocumentCommand("copy");
case "paste":
return editor ? runMonacoEditorAction(editor, "editor.action.clipboardPasteAction") : runDocumentCommand("paste");
case "delete":
return editor ? runMonacoEditorAction(editor, "deleteLeft") : runDocumentCommand("delete");
case "select_all":
return editor ? runMonacoEditorAction(editor, "editor.action.selectAll") : runDocumentCommand("selectAll");
case "find":
return editor ? runMonacoEditorAction(editor, "actions.find") : false;
case "replace":
return editor ? runMonacoEditorAction(editor, "editor.action.startFindReplaceAction") : false;
case "reload":
case "force_reload":
window.location.reload();
return true;
case "reset_zoom":
applyAppZoom(1);
return true;
case "zoom_in":
applyAppZoom((window.__bdsAppZoom || 1) + 0.1);
return true;
case "zoom_out":
applyAppZoom((window.__bdsAppZoom || 1) - 0.1);
return true;
case "toggle_full_screen":
if (document.fullscreenElement) {
document.exitFullscreen?.();
} else {
document.documentElement.requestFullscreen?.();
}
return true;
default:
return false;
}
};
// js/hooks/app_shell.js
var AppShell = {
mounted() {
this.shortcuts = parseShortcutConfig(this.el.dataset.shortcuts);
this.currentProjectId = this.el.dataset.projectId || "";
this.syncStoredLayout();
this.syncStoredUiLanguage();
this.destroyOverlaySync = syncTitlebarOverlayInsets();
this.workbenchStorageKey = (projectId) => projectId ? `${WORKBENCH_SESSION_STORAGE_KEY_PREFIX}${projectId}` : null;
this.restoreStoredWorkbenchSession = () => {
const projectId = this.el.dataset.projectId || "";
const storageKey = this.workbenchStorageKey(projectId);
if (!storageKey) {
return false;
}
const session = parseJsonObject(window.localStorage.getItem(storageKey));
if (!session) {
return false;
}
this.pushEvent("restore_workbench_session", { session });
return true;
};
this.persistWorkbenchSession = () => {
const projectId = this.el.dataset.projectId || "";
const storageKey = this.workbenchStorageKey(projectId);
const session = this.el.dataset.workbenchSession;
if (!storageKey || !session) {
return;
}
window.localStorage.setItem(storageKey, session);
};
this.handleMouseDown = (event) => {
const handle = event.target.closest("[data-role='resize-handle']");
if (!handle || !this.el.contains(handle)) {
return;
}
event.preventDefault();
const target = handle.dataset.resize;
const startX = event.clientX;
const startWidth = target === "assistant" ? shellWidth("[data-testid='assistant-shell']") : shellWidth("[data-testid='sidebar-shell']");
const min = target === "assistant" ? 280 : 200;
const max = target === "assistant" ? 640 : 500;
const invert = target === "assistant";
const onMouseMove = (moveEvent) => {
const delta = invert ? startX - moveEvent.clientX : moveEvent.clientX - startX;
const width = clamp(startWidth + delta, min, max);
const selector = target === "assistant" ? "[data-testid='assistant-shell']" : "[data-testid='sidebar-shell']";
setShellWidth(selector, width);
persistWidth(target, width);
};
const onMouseUp = (upEvent) => {
const delta = invert ? startX - upEvent.clientX : upEvent.clientX - startX;
const width = clamp(startWidth + delta, min, max);
persistWidth(target, width);
this.pushEvent("resize_panel", { target, width });
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
};
this.el.addEventListener("mousedown", this.handleMouseDown);
this.handleNativeMenuAction = (event) => {
const action = event.detail?.action;
const ackId = event.detail?.ackId;
if (action) {
this.pushEvent("native_menu_action", { action }, () => {
if (ackId) {
window.dispatchEvent(
new CustomEvent("bds:native-menu-action-ack", { detail: { ackId } })
);
}
});
}
};
this.handleChange = (event) => {
const select = event.target.closest(".status-bar-language-select");
if (select && this.el.contains(select)) {
window.localStorage.setItem(UI_LANGUAGE_STORAGE_KEY, select.value);
}
};
this.handleShortcutKeyDown = (event) => {
if (shortcutTargetIsEditable(event)) {
return;
}
const shortcut = this.shortcuts.find((candidate) => shortcutMatchesEvent(candidate, event));
if (!shortcut) {
return;
}
event.preventDefault();
event.stopPropagation();
this.pushEvent("shortcut", {
key: normalizeShortcutKey(event.key),
meta: event.metaKey,
ctrl: event.ctrlKey,
alt: event.altKey,
shift: event.shiftKey,
tag: event.target?.tagName || null,
contentEditable: event.target?.isContentEditable || false
});
};
this.handleThumbnailLoad = (event) => {
if (event.target instanceof HTMLImageElement && event.target.classList.contains("media-thumbnail-image")) {
setMediaThumbnailLoaded(event.target, true);
}
};
this.handleThumbnailError = (event) => {
if (event.target instanceof HTMLImageElement && event.target.classList.contains("media-thumbnail-image")) {
setMediaThumbnailLoaded(event.target, false);
}
};
this.handleEvent("menu-runtime-command", ({ action }) => {
if (action) {
runMenuRuntimeCommand(String(action));
}
});
window.addEventListener("bds:native-menu-action", this.handleNativeMenuAction);
window.addEventListener("keydown", this.handleShortcutKeyDown, true);
this.el.addEventListener("load", this.handleThumbnailLoad, true);
this.el.addEventListener("error", this.handleThumbnailError, true);
this.el.addEventListener("change", this.handleChange);
syncMediaThumbnailState(this.el);
this.restoreStoredWorkbenchSession();
},
updated() {
const nextProjectId = this.el.dataset.projectId || "";
if (nextProjectId !== this.currentProjectId) {
this.currentProjectId = nextProjectId;
if (this.restoreStoredWorkbenchSession()) {
return;
}
}
syncMediaThumbnailState(this.el);
this.persistWorkbenchSession();
},
destroyed() {
this.el.removeEventListener("mousedown", this.handleMouseDown);
this.el.removeEventListener("load", this.handleThumbnailLoad, true);
this.el.removeEventListener("error", this.handleThumbnailError, true);
this.el.removeEventListener("change", this.handleChange);
window.removeEventListener("bds:native-menu-action", this.handleNativeMenuAction);
window.removeEventListener("keydown", this.handleShortcutKeyDown, true);
if (this.destroyOverlaySync) {
this.destroyOverlaySync();
}
},
syncStoredLayout() {
this.pushEvent("sync_layout", {
sidebar_width: readStoredSize(SIDEBAR_STORAGE_KEY, shellWidth("[data-testid='sidebar-shell']"), 200, 500),
assistant_sidebar_width: readStoredSize(ASSISTANT_STORAGE_KEY, 360, 280, 640)
});
},
syncStoredUiLanguage() {
const stored = window.localStorage.getItem(UI_LANGUAGE_STORAGE_KEY);
if (stored) {
this.pushEvent("sync_ui_language", { language: stored });
}
}
};
// js/hooks/sidebar_interactions.js
var SidebarInteractions = {
mounted() {
this.handleDblClick = (event) => {
const button = event.target.closest("[data-testid='sidebar-open-item']");
if (!button || !this.el.contains(button)) {
return;
}
this.pushEvent("pin_sidebar_item", {
route: button.dataset.route,
id: button.dataset.itemId,
title: button.dataset.openTitle || "",
subtitle: button.dataset.openSubtitle || ""
});
};
this.el.addEventListener("dblclick", this.handleDblClick);
},
destroyed() {
this.el.removeEventListener("dblclick", this.handleDblClick);
}
};
// js/hooks/section_scroll.js
var makeSectionScrollHook = (datasetKey) => ({
mounted() {
this.lastTargetId = null;
this.scrollToSelectedSection();
},
updated() {
this.scrollToSelectedSection();
},
scrollToSelectedSection() {
const targetId = this.el.dataset[datasetKey];
if (!targetId || targetId === this.lastTargetId) {
return;
}
this.lastTargetId = targetId;
window.requestAnimationFrame(() => {
const target = document.getElementById(targetId);
if (target && this.el.contains(target)) {
target.scrollIntoView({ block: "start", behavior: "smooth" });
}
});
}
});
var SettingsSectionScroll = makeSectionScrollHook("settingsScrollTarget");
var TagsSectionScroll = makeSectionScrollHook("tagsScrollTarget");
// js/hooks/chat_surface.js
var ChatSurface = {
mounted() {
this.stickToBottom = true;
this.scrollContainer = null;
this.autoResize = () => {
const textarea = this.el.querySelector(".chat-input");
if (!textarea) {
return;
}
const styles = getComputedStyle(textarea);
const minHeight = parseFloat(styles.getPropertyValue("--chat-input-min-height")) || 20;
const maxHeight = parseFloat(styles.getPropertyValue("--chat-input-max-height")) || 160;
textarea.rows = 1;
textarea.style.minHeight = `${minHeight}px`;
if (textarea.value.trim() === "") {
textarea.style.height = `${minHeight}px`;
textarea.style.maxHeight = `${minHeight}px`;
textarea.style.overflowY = "hidden";
return;
}
textarea.style.maxHeight = `${maxHeight}px`;
textarea.style.height = "0px";
const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
textarea.style.height = `${nextHeight}px`;
textarea.style.overflowY = nextHeight >= maxHeight ? "auto" : "hidden";
};
this.syncScrollContainer = () => {
const nextContainer = this.el.querySelector(".chat-messages");
if (nextContainer === this.scrollContainer) {
return;
}
if (this.scrollContainer) {
this.scrollContainer.removeEventListener("scroll", this.handleScroll);
}
this.scrollContainer = nextContainer;
if (this.scrollContainer) {
this.scrollContainer.addEventListener("scroll", this.handleScroll);
}
};
this.scrollToBottom = (force = false) => {
if (!this.scrollContainer) {
return;
}
if (force || this.stickToBottom) {
this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight;
}
};
this.syncExpandedSurfaces = () => {
this.el.querySelectorAll(".chat-inline-surface[data-expanded='true']").forEach((surface) => {
surface.open = true;
});
};
this.surfaceObserver = new MutationObserver(() => {
this.syncExpandedSurfaces();
});
this.handleScroll = () => {
if (!this.scrollContainer) {
this.stickToBottom = true;
return;
}
const distanceFromBottom = this.scrollContainer.scrollHeight - this.scrollContainer.scrollTop - this.scrollContainer.clientHeight;
this.stickToBottom = distanceFromBottom < 48;
};
this.handleInput = (event) => {
if (!event.target.closest(".chat-input")) {
return;
}
this.stickToBottom = true;
this.autoResize();
};
this.handleKeyDown = (event) => {
if (!event.target.closest(".chat-input")) {
return;
}
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
event.preventDefault();
const sendButton = this.el.querySelector("[data-testid='chat-send-button']");
if (sendButton && !sendButton.disabled) {
sendButton.click();
}
}
};
this.el.addEventListener("input", this.handleInput);
this.el.addEventListener("keydown", this.handleKeyDown);
this.syncScrollContainer();
this.syncExpandedSurfaces();
this.surfaceObserver.observe(this.el, { childList: true, subtree: true });
this.autoResize();
window.requestAnimationFrame(() => this.scrollToBottom(true));
},
updated() {
this.syncScrollContainer();
this.syncExpandedSurfaces();
this.autoResize();
window.requestAnimationFrame(() => this.scrollToBottom());
},
destroyed() {
this.surfaceObserver.disconnect();
this.el.removeEventListener("input", this.handleInput);
this.el.removeEventListener("keydown", this.handleKeyDown);
if (this.scrollContainer) {
this.scrollContainer.removeEventListener("scroll", this.handleScroll);
}
}
};
// js/hooks/menu_editor_tree.js
var MenuEditorTree = {
mounted() {
this.dragItemId = null;
this.dragSourceEl = null;
this.dropTargetEl = null;
this.dropPosition = null;
this.clearDropTarget = () => {
if (this.dropTargetEl) {
this.dropTargetEl.classList.remove("is-drop-before", "is-drop-after", "is-drop-inside");
}
this.dropTargetEl = null;
this.dropPosition = null;
};
this.setDropTarget = (row, position) => {
if (this.dropTargetEl === row && this.dropPosition === position) {
return;
}
this.clearDropTarget();
this.dropTargetEl = row;
this.dropPosition = position;
row.classList.add(`is-drop-${position}`);
};
this.handleDragStart = (event) => {
const handle = event.target.closest("[data-menu-drag-handle='true']");
const row = event.target.closest("[data-menu-item-id]");
if (!handle || !row || !this.el.contains(row)) {
return;
}
this.dragItemId = row.dataset.menuItemId || null;
this.dragSourceEl = row;
row.classList.add("is-dragging");
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", this.dragItemId || "");
}
};
this.handleDragOver = (event) => {
const row = event.target.closest("[data-menu-item-id]");
if (!this.dragItemId || !row || !this.el.contains(row)) {
this.clearDropTarget();
return;
}
const targetItemId = row.dataset.menuItemId || "";
if (!targetItemId || targetItemId === this.dragItemId) {
this.clearDropTarget();
return;
}
event.preventDefault();
const rect = row.getBoundingClientRect();
const offsetY = event.clientY - rect.top;
const allowInside = row.dataset.menuCanDropInside === "true";
const insideBandTop = rect.height * 0.3;
const insideBandBottom = rect.height * 0.7;
const position = allowInside && offsetY >= insideBandTop && offsetY <= insideBandBottom ? "inside" : offsetY < rect.height / 2 ? "before" : "after";
this.setDropTarget(row, position);
if (event.dataTransfer) {
event.dataTransfer.dropEffect = "move";
}
};
this.handleDrop = (event) => {
const row = event.target.closest("[data-menu-item-id]");
if (!this.dragItemId || !row || !this.el.contains(row) || !this.dropPosition) {
this.clearDropTarget();
return;
}
event.preventDefault();
this.pushEvent("menu_editor_drop_item", {
drag_item_id: this.dragItemId,
target_item_id: row.dataset.menuItemId,
position: this.dropPosition
});
this.clearDropTarget();
};
this.handleDragLeave = (event) => {
const related = event.relatedTarget;
if (this.dropTargetEl && (!related || !this.dropTargetEl.contains(related))) {
this.clearDropTarget();
}
};
this.handleDragEnd = () => {
if (this.dragSourceEl) {
this.dragSourceEl.classList.remove("is-dragging");
}
this.dragItemId = null;
this.dragSourceEl = null;
this.clearDropTarget();
};
this.el.addEventListener("dragstart", this.handleDragStart);
this.el.addEventListener("dragover", this.handleDragOver);
this.el.addEventListener("drop", this.handleDrop);
this.el.addEventListener("dragleave", this.handleDragLeave);
this.el.addEventListener("dragend", this.handleDragEnd);
},
destroyed() {
this.el.removeEventListener("dragstart", this.handleDragStart);
this.el.removeEventListener("dragover", this.handleDragOver);
this.el.removeEventListener("drop", this.handleDrop);
this.el.removeEventListener("dragleave", this.handleDragLeave);
this.el.removeEventListener("dragend", this.handleDragEnd);
}
};
// js/hooks/monaco_editor.js
var MonacoEditor = {
mounted() {
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
this.host = this.el.querySelector(".monaco-editor-instance");
this.language = this.el.dataset.monacoLanguage || "plaintext";
this.wordWrap = this.el.dataset.monacoWordWrap || "off";
this.editorId = this.el.dataset.monacoEditorId || "";
this.insertEvent = this.el.dataset.monacoInsertEvent || "";
this.syncTimer = null;
this.isApplyingRemoteUpdate = false;
this.lastKnownValue = this.textarea?.value || "";
this.syncEditorFromTextarea = () => {
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
if (!this.textarea || !this.editor) {
return;
}
const value = this.textarea.value || "";
if (this.editor.getValue() !== value) {
this.isApplyingRemoteUpdate = true;
this.editor.setValue(value);
this.isApplyingRemoteUpdate = false;
}
this.lastKnownValue = value;
};
this.layoutEditorSoon = () => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
if (!this.editor) {
return;
}
this.editor.layout();
});
});
};
this.waitForMonacoVisibleSize = () => new Promise((resolve) => {
let settled = false;
let attempts = 0;
const hasVisibleSize = () => {
const rect = this.host?.getBoundingClientRect();
return Boolean(rect && rect.width > 0 && rect.height > 0);
};
const finish = () => {
if (settled) {
return;
}
settled = true;
this.visibleSizeObserver?.disconnect();
this.visibleSizeObserver = null;
resolve();
};
const check = () => {
if (hasVisibleSize() || attempts >= 20) {
finish();
return;
}
attempts += 1;
window.requestAnimationFrame(check);
};
if (hasVisibleSize()) {
finish();
return;
}
if (window.ResizeObserver && this.host) {
this.visibleSizeObserver = new ResizeObserver(() => {
if (hasVisibleSize()) {
finish();
}
});
this.visibleSizeObserver.observe(this.host);
}
window.requestAnimationFrame(check);
});
this.queueSync = () => {
if (!this.textarea || !this.editor) {
return;
}
window.clearTimeout(this.syncTimer);
this.syncTimer = window.setTimeout(() => {
if (!this.textarea || !this.editor) {
return;
}
const value = this.editor.getValue();
if (this.textarea.value === value) {
return;
}
this.lastKnownValue = value;
this.textarea.value = value;
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
}, 120);
};
this.handleInsert = ({ id, content }) => {
if (!this.editor || !content || String(id) !== String(this.editorId)) {
return;
}
const model = this.editor.getModel();
const selection = this.editor.getSelection();
if (!model || !selection) {
return;
}
const value = this.editor.getValue();
const start = model.getOffsetAt(selection.getStartPosition());
const end = model.getOffsetAt(selection.getEndPosition());
const before = value.slice(0, start);
const after = value.slice(end);
const separator = before !== "" && !before.endsWith("\n") ? "\n" : "";
const suffix = after !== "" && !content.endsWith("\n") ? "\n" : "";
const inserted = `${separator}${content}${suffix}`;
this.editor.executeEdits("bds-insert-content", [
{
range: selection,
text: inserted,
forceMoveMarkers: true
}
]);
this.editor.focus();
};
loadMonaco().then(async (monaco) => {
if (!this.host || !this.textarea) {
return;
}
await this.waitForMonacoVisibleSize();
ensureMonacoTheme(monaco);
this.editor = monaco.editor.create(this.host, {
value: this.textarea.value || "",
language: this.language,
theme: "bds-theme",
automaticLayout: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
wordWrap: this.wordWrap,
lineNumbers: "on",
lineNumbersMinChars: 3,
fontSize: 14,
fontFamily: "'Cascadia Code', 'Consolas', 'Courier New', monospace",
padding: { top: 12, bottom: 12 },
roundedSelection: false,
renderLineHighlight: "line",
formatOnPaste: true,
cursorStyle: "line",
cursorBlinking: "smooth",
quickSuggestions: this.language === "markdown-with-macros" ? false : true,
tabSize: 2,
insertSpaces: true
});
registerMonacoEditor(this.editorId || this.el.id, this.editor);
monaco.editor.setTheme("bds-theme");
this.syncEditorFromTextarea();
this.layoutEditorSoon();
this.changeSubscription = this.editor.onDidChangeModelContent(() => {
if (this.isApplyingRemoteUpdate) {
return;
}
this.queueSync();
});
if (this.insertEvent) {
this.handleEvent(this.insertEvent, this.handleInsert);
}
}).catch((error) => {
console.error("Failed to load Monaco editor", error);
});
},
updated() {
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
this.host = this.el.querySelector(".monaco-editor-instance");
this.language = this.el.dataset.monacoLanguage || this.language || "plaintext";
this.wordWrap = this.el.dataset.monacoWordWrap || this.wordWrap || "off";
if (!this.editor || !this.textarea) {
return;
}
loadMonaco().then((monaco) => {
ensureMonacoTheme(monaco);
monaco.editor.setTheme("bds-theme");
if (this.editor.getModel()?.getLanguageId() !== this.language) {
monaco.editor.setModelLanguage(this.editor.getModel(), this.language);
}
this.editor.updateOptions({ wordWrap: this.wordWrap });
});
this.syncEditorFromTextarea();
this.layoutEditorSoon();
},
destroyed() {
window.clearTimeout(this.syncTimer);
this.visibleSizeObserver?.disconnect();
this.changeSubscription?.dispose();
unregisterMonacoEditor(this.editorId || this.el.id);
this.editor?.dispose();
}
};
// js/hooks/monaco_diff_editor.js
var MonacoDiffEditor = {
mounted() {
this.host = this.el.querySelector(".monaco-diff-editor-instance");
this.originalInput = this.el.querySelector(".monaco-diff-original");
this.modifiedInput = this.el.querySelector(".monaco-diff-modified");
this.filePath = this.el.dataset.monacoDiffFilePath || "working-tree";
this.language = this.el.dataset.monacoDiffLanguage || "plaintext";
this.viewStyle = this.el.dataset.monacoDiffViewStyle || "inline";
this.wordWrap = this.el.dataset.monacoDiffWordWrap || "off";
this.hideUnchanged = this.el.dataset.monacoDiffHideUnchanged === "true";
this.readValues = () => ({
original: this.originalInput?.value || "",
modified: this.modifiedInput?.value || ""
});
this.applyDataset = () => {
this.filePath = this.el.dataset.monacoDiffFilePath || "working-tree";
this.language = this.el.dataset.monacoDiffLanguage || "plaintext";
this.viewStyle = this.el.dataset.monacoDiffViewStyle || "inline";
this.wordWrap = this.el.dataset.monacoDiffWordWrap || "off";
this.hideUnchanged = this.el.dataset.monacoDiffHideUnchanged === "true";
};
this.setModels = (monaco) => {
const values = this.readValues();
this.originalModel?.dispose();
this.modifiedModel?.dispose();
this.originalModel = monaco.editor.createModel(
values.original,
this.language,
monaco.Uri.parse(diffModelPath(this.filePath, "original"))
);
this.modifiedModel = monaco.editor.createModel(
values.modified,
this.language,
monaco.Uri.parse(diffModelPath(this.filePath, "modified"))
);
this.editor.setModel({ original: this.originalModel, modified: this.modifiedModel });
this.lastFilePath = this.filePath;
};
loadMonaco().then((monaco) => {
if (!this.host) {
return;
}
ensureMonacoTheme(monaco);
this.editor = monaco.editor.createDiffEditor(this.host, {
theme: "bds-theme",
automaticLayout: true,
readOnly: true,
renderSideBySide: this.viewStyle === "side-by-side",
minimap: { enabled: false },
scrollBeyondLastLine: false,
lineNumbers: "on",
diffCodeLens: false,
originalEditable: false,
wordWrap: this.wordWrap,
hideUnchangedRegions: { enabled: this.hideUnchanged },
ignoreTrimWhitespace: false
});
this.setModels(monaco);
}).catch((error) => {
console.error("Failed to load Monaco diff editor", error);
});
},
updated() {
this.host = this.el.querySelector(".monaco-diff-editor-instance");
this.originalInput = this.el.querySelector(".monaco-diff-original");
this.modifiedInput = this.el.querySelector(".monaco-diff-modified");
this.applyDataset();
if (!this.editor) {
return;
}
loadMonaco().then((monaco) => {
ensureMonacoTheme(monaco);
monaco.editor.setTheme("bds-theme");
this.editor.updateOptions({
renderSideBySide: this.viewStyle === "side-by-side",
wordWrap: this.wordWrap,
hideUnchangedRegions: { enabled: this.hideUnchanged }
});
if (this.lastFilePath !== this.filePath) {
this.setModels(monaco);
return;
}
const values = this.readValues();
if (this.originalModel && this.originalModel.getLanguageId() !== this.language) {
monaco.editor.setModelLanguage(this.originalModel, this.language);
}
if (this.modifiedModel && this.modifiedModel.getLanguageId() !== this.language) {
monaco.editor.setModelLanguage(this.modifiedModel, this.language);
}
if (this.originalModel && this.originalModel.getValue() !== values.original) {
this.originalModel.setValue(values.original);
}
if (this.modifiedModel && this.modifiedModel.getValue() !== values.modified) {
this.modifiedModel.setValue(values.modified);
}
});
},
destroyed() {
this.originalModel?.dispose();
this.modifiedModel?.dispose();
this.editor?.dispose();
}
};
// js/hooks/index.js
var Hooks2 = {
AppShell,
SidebarInteractions,
SettingsSectionScroll,
TagsSectionScroll,
ChatSurface,
MenuEditorTree,
MonacoEditor,
MonacoDiffEditor
};
// js/app.js
document.addEventListener("DOMContentLoaded", () => {
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
const liveSocket = new LiveSocket2("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: Hooks2,
metadata: {
keydown: (event) => ({
key: event.key,
meta: event.metaKey,
ctrl: event.ctrlKey,
alt: event.altKey,
shift: event.shiftKey,
tag: event.target?.tagName || null,
contentEditable: event.target?.isContentEditable || false
})
}
});
liveSocket.connect();
window.liveSocket = liveSocket;
});
})();