prep for 1.9.11 release

This commit is contained in:
Carson Gross 2024-03-13 18:31:46 -06:00
parent ef791c51eb
commit 11799bca6c
18 changed files with 21190 additions and 31417 deletions

39
dist/ext/sse.js vendored
View File

@ -51,7 +51,6 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// Try to create EventSources when elements are processed // Try to create EventSources when elements are processed
case "htmx:afterProcessNode": case "htmx:afterProcessNode":
ensureEventSourceOnElement(evt.target); ensureEventSourceOnElement(evt.target);
registerSSE(evt.target);
} }
} }
}); });
@ -112,19 +111,18 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
* @param {HTMLElement} elt * @param {HTMLElement} elt
*/ */
function registerSSE(elt) { function registerSSE(elt) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
// Add message handlers for every `sse-swap` attribute // Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) { queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function (child) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
var sseSwapAttr = api.getAttributeValue(child, "sse-swap"); var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
if (sseSwapAttr) { if (sseSwapAttr) {
@ -145,9 +143,13 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// If the body no longer contains the element, remove the listener // If the body no longer contains the element, remove the listener
if (!api.bodyContains(child)) { if (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener); source.removeEventListener(sseEventName, listener);
return;
} }
// swap the response into the DOM and trigger a notification // swap the response into the DOM and trigger a notification
if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
return;
}
swap(child, event.data); swap(child, event.data);
api.triggerEvent(elt, "htmx:sseMessage", event); api.triggerEvent(elt, "htmx:sseMessage", event);
}; };
@ -160,6 +162,16 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// Add message handlers for every `hx-trigger="sse:*"` attribute // Add message handlers for every `hx-trigger="sse:*"` attribute
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) { queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
var sseEventName = api.getAttributeValue(child, "hx-trigger"); var sseEventName = api.getAttributeValue(child, "hx-trigger");
if (sseEventName == null) { if (sseEventName == null) {
@ -220,6 +232,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
ensureEventSource(child, sseURL, retryCount); ensureEventSource(child, sseURL, retryCount);
}); });
registerSSE(elt);
} }
function ensureEventSource(elt, url, retryCount) { function ensureEventSource(elt, url, retryCount) {

55
dist/htmx.d.ts vendored
View File

@ -21,7 +21,7 @@ export function addClass(elt: Element, clazz: string, delay?: number): void;
* @param element the element to target (defaults to the **body**) * @param element the element to target (defaults to the **body**)
* @returns Promise that resolves immediately if no request is sent, or when the request is complete * @returns Promise that resolves immediately if no request is sent, or when the request is complete
*/ */
export function ajax(verb: string, path: string, element: Element): Promise<void>; export function ajax(verb: string, path: string, element?: Element): Promise<void>;
/** /**
* Issues an htmx-style AJAX request * Issues an htmx-style AJAX request
@ -429,20 +429,57 @@ export interface HtmxConfig {
* If set to true htmx will not update the title of the document when a title tag is found in new content * If set to true htmx will not update the title of the document when a title tag is found in new content
* @default false * @default false
*/ */
ignoreTitle:? boolean; ignoreTitle?: boolean;
/**
* The cache to store evaluated trigger specifications into.
* You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
* @default null
*/
triggerSpecsCache?: {[trigger: string]: HtmxTriggerSpecification[]};
} }
export type HtmxEvent = "htmx:abort"
| "htmx:afterOnLoad"
| "htmx:afterProcessNode"
| "htmx:afterRequest"
| "htmx:afterSettle"
| "htmx:afterSwap"
| "htmx:beforeCleanupElement"
| "htmx:beforeOnLoad"
| "htmx:beforeProcessNode"
| "htmx:beforeRequest"
| "htmx:beforeSwap"
| "htmx:beforeSend"
| "htmx:configRequest"
| "htmx:confirm"
| "htmx:historyCacheError"
| "htmx:historyCacheMiss"
| "htmx:historyCacheMissError"
| "htmx:historyCacheMissLoad"
| "htmx:historyRestore"
| "htmx:load"
| "htmx:noSSESourceError"
| "htmx:onLoadError"
| "htmx:oobAfterSwap"
| "htmx:oobBeforeSwap"
| "htmx:oobErrorNoTarget"
| "htmx:prompt"
| "htmx:pushedIntoHistory"
| "htmx:responseError"
| "htmx:sendError"
| "htmx:sseError"
| "htmx:sseOpen"
| "htmx:swapError"
| "htmx:targetError"
| "htmx:timeout"
| "htmx:validation:validate"
| "htmx:validation:failed"
| "htmx:validation:halted"
| "htmx:xhr:abort"
| "htmx:xhr:loadend"
| "htmx:xhr:loadstart"
| "htmx:xhr:progress"
;
/** /**
* https://htmx.org/extensions/#defining * https://htmx.org/extensions/#defining
*/ */
export interface HtmxExtension { export interface HtmxExtension {
onEvent?: (name: string, evt: CustomEvent) => any; onEvent?: (name: HtmxEvent, evt: CustomEvent) => any;
transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any; transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any;
isInlineSwap?: (swapStyle: any) => any; isInlineSwap?: (swapStyle: any) => any;
handleSwap?: (swapStyle: any, target: any, fragment: any, settleInfo: any) => any; handleSwap?: (swapStyle: any, target: any, fragment: any, settleInfo: any) => any;

35
dist/htmx.js vendored
View File

@ -89,7 +89,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType; sock.binaryType = htmx.config.wsBinaryType;
return sock; return sock;
}, },
version: "1.9.10" version: "1.9.11"
}; };
/** @type {import("./htmx").HtmxInternalApi} */ /** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, ''); content = content.replace(HEAD_TAG_REGEX, '');
} }
if (htmx.config.useTemplateFragments && partialResponse) { if (htmx.config.useTemplateFragments && partialResponse) {
var documentFragment = parseHTML("<body><template>" + content + "</template></body>", 0); var fragment = parseHTML("<body><template>" + content + "</template></body>", 0);
// @ts-ignore type mismatch between DocumentFragment and Element. // @ts-ignore type mismatch between DocumentFragment and Element.
// TODO: Are these close enough for htmx to use interchangeably? // TODO: Are these close enough for htmx to use interchangeably?
return documentFragment.querySelector('template').content; if (htmx.config.allowScriptTags) {
// if there is a nonce set up, set it on the new script tags
forEach(fragment.querySelectorAll("script"), function (script) {
if (htmx.config.inlineScriptNonce) {
script.nonce = htmx.config.inlineScriptNonce;
}
getInternalData(script).executed = true; // mark as executed due to template insertion semantics
})
} else {
forEach(fragment.querySelectorAll("script"), function (script) {
// remove all script tags if scripts are disabled
removeElement(script);
})
}
return fragment.querySelector('template').content;
} }
switch (startTag) { switch (startTag) {
case "thead": case "thead":
@ -1892,7 +1906,8 @@ return (function () {
} }
function evalScript(script) { function evalScript(script) {
if (htmx.config.allowScriptTags && (script.type === "text/javascript" || script.type === "module" || script.type === "") ) { if (!getInternalData(script).executed && htmx.config.allowScriptTags &&
(script.type === "text/javascript" || script.type === "module" || script.type === "") ) {
var newScript = getDocument().createElement("script"); var newScript = getDocument().createElement("script");
forEach(script.attributes, function (attr) { forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value); newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
} }
function processScripts(elt) { function processScripts(elt) {
if (matches(elt, "script")) { if (!htmx.config.useTemplateFragments) {
evalScript(elt); if (matches(elt, "script")) {
evalScript(elt);
}
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
function shouldProcessHxOn(elt) { function shouldProcessHxOn(elt) {

2
dist/htmx.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/htmx.min.js.gz vendored

Binary file not shown.

View File

@ -87,8 +87,8 @@
, 'expected ' + elToString(el) + ' not to have class matching #{exp}' , 'expected ' + elToString(el) + ' not to have class matching #{exp}'
, className , className
) )
} }
this.assert( this.assert(
el.classList.contains(className) el.classList.contains(className)
, 'expected ' + elToString(el) + ' to have class #{exp}' , 'expected ' + elToString(el) + ' to have class #{exp}'
@ -350,7 +350,7 @@
chai.Assertion.addProperty('displayed', function() { chai.Assertion.addProperty('displayed', function() {
var el = flag(this, 'object'), var el = flag(this, 'object'),
actual = document.body.contains(el) ? window.getComputedStyle(el).display : el.style.display actual = el.getRootNode({ composed: true }) === document ? window.getComputedStyle(el).display : el.style.display
this.assert( this.assert(
actual !== 'none' actual !== 'none'

114
www/static/node_modules/chai/chai.js generated vendored
View File

@ -14,7 +14,7 @@ var used = [];
* Chai version * Chai version
*/ */
exports.version = '4.3.3'; exports.version = '4.3.8';
/*! /*!
* Assertion Error * Assertion Error
@ -8608,6 +8608,7 @@ var config = require('../config');
* messages or should be truncated. * messages or should be truncated.
* *
* @param {Mixed} javascript object to inspect * @param {Mixed} javascript object to inspect
* @returns {string} stringified object
* @name objDisplay * @name objDisplay
* @namespace Utils * @namespace Utils
* @api public * @api public
@ -9062,7 +9063,7 @@ var flag = require('./flag');
/** /**
* ### .test(object, expression) * ### .test(object, expression)
* *
* Test and object for expression. * Test an object for expression.
* *
* @param {Object} object (constructed Assertion) * @param {Object} object (constructed Assertion)
* @param {Arguments} chai.Assertion.prototype.assert arguments * @param {Arguments} chai.Assertion.prototype.assert arguments
@ -9250,6 +9251,7 @@ AssertionError.prototype.toJSON = function (stack) {
* MIT Licensed * MIT Licensed
*/ */
var getFunctionName = require('get-func-name');
/** /**
* ### .checkError * ### .checkError
* *
@ -9329,34 +9331,6 @@ function compatibleMessage(thrown, errMatcher) {
return false; return false;
} }
/**
* ### .getFunctionName(constructorFn)
*
* Returns the name of a function.
* This also includes a polyfill function if `constructorFn.name` is not defined.
*
* @name getFunctionName
* @param {Function} constructorFn
* @namespace Utils
* @api private
*/
var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\(\/]+)/;
function getFunctionName(constructorFn) {
var name = '';
if (typeof constructorFn.name === 'undefined') {
// Here we run a polyfill if constructorFn.name is not defined
var match = String(constructorFn).match(functionNameMatch);
if (match) {
name = match[1];
}
} else {
name = constructorFn.name;
}
return name;
}
/** /**
* ### .getConstructorName(errorLike) * ### .getConstructorName(errorLike)
* *
@ -9376,8 +9350,11 @@ function getConstructorName(errorLike) {
// If `err` is not an instance of Error it is an error constructor itself or another function. // If `err` is not an instance of Error it is an error constructor itself or another function.
// If we've got a common function we get its name, otherwise we may need to create a new instance // If we've got a common function we get its name, otherwise we may need to create a new instance
// of the error just in case it's a poorly-constructed error. Please see chaijs/chai/issues/45 to know more. // of the error just in case it's a poorly-constructed error. Please see chaijs/chai/issues/45 to know more.
constructorName = getFunctionName(errorLike).trim() || constructorName = getFunctionName(errorLike);
getFunctionName(new errorLike()); // eslint-disable-line new-cap if (constructorName === '') {
var newConstructorName = getFunctionName(new errorLike()); // eslint-disable-line new-cap
constructorName = newConstructorName || constructorName;
}
} }
return constructorName; return constructorName;
@ -9415,7 +9392,7 @@ module.exports = {
getConstructorName: getConstructorName, getConstructorName: getConstructorName,
}; };
},{}],35:[function(require,module,exports){ },{"get-func-name":36}],35:[function(require,module,exports){
'use strict'; 'use strict';
/* globals Symbol: false, Uint8Array: false, WeakMap: false */ /* globals Symbol: false, Uint8Array: false, WeakMap: false */
/*! /*!
@ -9810,8 +9787,15 @@ function getEnumerableKeys(target) {
return keys; return keys;
} }
function getNonEnumerableSymbols(target) { function getEnumerableSymbols(target) {
var keys = Object.getOwnPropertySymbols(target); var keys = [];
var allKeys = Object.getOwnPropertySymbols(target);
for (var i = 0; i < allKeys.length; i += 1) {
var key = allKeys[i];
if (Object.getOwnPropertyDescriptor(target, key).enumerable) {
keys.push(key);
}
}
return keys; return keys;
} }
@ -9850,8 +9834,8 @@ function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
function objectEqual(leftHandOperand, rightHandOperand, options) { function objectEqual(leftHandOperand, rightHandOperand, options) {
var leftHandKeys = getEnumerableKeys(leftHandOperand); var leftHandKeys = getEnumerableKeys(leftHandOperand);
var rightHandKeys = getEnumerableKeys(rightHandOperand); var rightHandKeys = getEnumerableKeys(rightHandOperand);
var leftHandSymbols = getNonEnumerableSymbols(leftHandOperand); var leftHandSymbols = getEnumerableSymbols(leftHandOperand);
var rightHandSymbols = getNonEnumerableSymbols(rightHandOperand); var rightHandSymbols = getEnumerableSymbols(rightHandOperand);
leftHandKeys = leftHandKeys.concat(leftHandSymbols); leftHandKeys = leftHandKeys.concat(leftHandSymbols);
rightHandKeys = rightHandKeys.concat(rightHandSymbols); rightHandKeys = rightHandKeys.concat(rightHandSymbols);
@ -9927,6 +9911,7 @@ function mapSymbols(arr) {
var toString = Function.prototype.toString; var toString = Function.prototype.toString;
var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\s\(\/]+)/; var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\s\(\/]+)/;
var maxFunctionSourceLength = 512;
function getFuncName(aFunc) { function getFuncName(aFunc) {
if (typeof aFunc !== 'function') { if (typeof aFunc !== 'function') {
return null; return null;
@ -9934,8 +9919,15 @@ function getFuncName(aFunc) {
var name = ''; var name = '';
if (typeof Function.prototype.name === 'undefined' && typeof aFunc.name === 'undefined') { if (typeof Function.prototype.name === 'undefined' && typeof aFunc.name === 'undefined') {
// eslint-disable-next-line prefer-reflect
var functionSource = toString.call(aFunc);
// To avoid unconstrained resource consumption due to pathalogically large function names,
// we limit the available return value to be less than 512 characters.
if (functionSource.indexOf('(') > maxFunctionSourceLength) {
return name;
}
// Here we run a polyfill if Function does not support the `name` property and if aFunc.name is not defined // Here we run a polyfill if Function does not support the `name` property and if aFunc.name is not defined
var match = toString.call(aFunc).match(functionNameMatch); var match = functionSource.match(functionNameMatch);
if (match) { if (match) {
name = match[1]; name = match[1];
} }
@ -10331,9 +10323,15 @@ module.exports = getFuncName;
} }
function inspectDate(dateObject, options) { function inspectDate(dateObject, options) {
// If we need to - truncate the time portion, but never the date var stringRepresentation = dateObject.toJSON();
var split = dateObject.toJSON().split('T');
var date = split[0]; if (stringRepresentation === null) {
return 'Invalid Date';
}
var split = stringRepresentation.split('T');
var date = split[0]; // If we need to - truncate the time portion, but never the date
return options.stylize("".concat(date, "T").concat(truncate(split[1], options.truncate - date.length - 1)), 'date'); return options.stylize("".concat(date, "T").concat(truncate(split[1], options.truncate - date.length - 1)), 'date');
} }
@ -10630,7 +10628,32 @@ module.exports = getFuncName;
nodeInspect = false; nodeInspect = false;
} }
var constructorMap = new WeakMap(); function FakeMap() {
// eslint-disable-next-line prefer-template
this.key = 'chai/loupe__' + Math.random() + Date.now();
}
FakeMap.prototype = {
// eslint-disable-next-line object-shorthand
get: function get(key) {
return key[this.key];
},
// eslint-disable-next-line object-shorthand
has: function has(key) {
return this.key in key;
},
// eslint-disable-next-line object-shorthand
set: function set(key, value) {
if (Object.isExtensible(key)) {
Object.defineProperty(key, this.key, {
// eslint-disable-next-line object-shorthand
value: value,
configurable: true
});
}
}
};
var constructorMap = new (typeof WeakMap === 'function' ? WeakMap : FakeMap)();
var stringTagMap = {}; var stringTagMap = {};
var baseTypesMap = { var baseTypesMap = {
undefined: function undefined$1(value, options) { undefined: function undefined$1(value, options) {
@ -10764,6 +10787,11 @@ module.exports = getFuncName;
} // If it is an object with an anonymous prototype, display it as an object. } // If it is an object with an anonymous prototype, display it as an object.
return inspectObject(value, options);
} // last chance to check if it's an object
if (value === Object(value)) {
return inspectObject(value, options); return inspectObject(value, options);
} // We have run out of options! Just stringify the value } // We have run out of options! Just stringify the value
@ -10775,7 +10803,7 @@ module.exports = getFuncName;
return false; return false;
} }
constructorMap.add(constructor, inspector); constructorMap.set(constructor, inspector);
return true; return true;
} }
function registerStringTag(stringTag, inspector) { function registerStringTag(stringTag, inspector) {

View File

@ -1,7 +1,73 @@
@charset "utf-8"; @charset "utf-8";
:root {
--mocha-color: #000;
--mocha-bg-color: #fff;
--mocha-pass-icon-color: #00d6b2;
--mocha-pass-color: #fff;
--mocha-pass-shadow-color: rgba(0,0,0,.2);
--mocha-pass-mediump-color: #c09853;
--mocha-pass-slow-color: #b94a48;
--mocha-test-pending-color: #0b97c4;
--mocha-test-pending-icon-color: #0b97c4;
--mocha-test-fail-color: #c00;
--mocha-test-fail-icon-color: #c00;
--mocha-test-fail-pre-color: #000;
--mocha-test-fail-pre-error-color: #c00;
--mocha-test-html-error-color: #000;
--mocha-box-shadow-color: #eee;
--mocha-box-bottom-color: #ddd;
--mocha-test-replay-color: #000;
--mocha-test-replay-bg-color: #eee;
--mocha-stats-color: #888;
--mocha-stats-em-color: #000;
--mocha-stats-hover-color: #eee;
--mocha-error-color: #c00;
--mocha-code-comment: #ddd;
--mocha-code-init: #2f6fad;
--mocha-code-string: #5890ad;
--mocha-code-keyword: #8a6343;
--mocha-code-number: #2f6fad;
}
@media (prefers-color-scheme: dark) {
:root {
--mocha-color: #fff;
--mocha-bg-color: #222;
--mocha-pass-icon-color: #00d6b2;
--mocha-pass-color: #222;
--mocha-pass-shadow-color: rgba(255,255,255,.2);
--mocha-pass-mediump-color: #f1be67;
--mocha-pass-slow-color: #f49896;
--mocha-test-pending-color: #0b97c4;
--mocha-test-pending-icon-color: #0b97c4;
--mocha-test-fail-color: #f44;
--mocha-test-fail-icon-color: #f44;
--mocha-test-fail-pre-color: #fff;
--mocha-test-fail-pre-error-color: #f44;
--mocha-test-html-error-color: #fff;
--mocha-box-shadow-color: #444;
--mocha-box-bottom-color: #555;
--mocha-test-replay-color: #fff;
--mocha-test-replay-bg-color: #444;
--mocha-stats-color: #aaa;
--mocha-stats-em-color: #fff;
--mocha-stats-hover-color: #444;
--mocha-error-color: #f44;
--mocha-code-comment: #ddd;
--mocha-code-init: #9cc7f1;
--mocha-code-string: #80d4ff;
--mocha-code-keyword: #e3a470;
--mocha-code-number: #4ca7ff;
}
}
body { body {
margin:0; margin:0;
background-color: var(--mocha-bg-color);
color: var(--mocha-color);
} }
#mocha { #mocha {
@ -69,11 +135,11 @@ body {
} }
#mocha .test.pass.medium .duration { #mocha .test.pass.medium .duration {
background: #c09853; background: var(--mocha-pass-mediump-color);
} }
#mocha .test.pass.slow .duration { #mocha .test.pass.slow .duration {
background: #b94a48; background: var(--mocha-pass-slow-color);
} }
#mocha .test.pass::before { #mocha .test.pass::before {
@ -82,17 +148,17 @@ body {
display: block; display: block;
float: left; float: left;
margin-right: 5px; margin-right: 5px;
color: #00d6b2; color: var(--mocha-pass-icon-color);
} }
#mocha .test.pass .duration { #mocha .test.pass .duration {
font-size: 9px; font-size: 9px;
margin-left: 5px; margin-left: 5px;
padding: 2px 5px; padding: 2px 5px;
color: #fff; color: var(--mocha-pass-color);
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); -webkit-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); -moz-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
box-shadow: inset 0 1px 1px rgba(0,0,0,.2); box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
-moz-border-radius: 5px; -moz-border-radius: 5px;
-ms-border-radius: 5px; -ms-border-radius: 5px;
@ -105,20 +171,20 @@ body {
} }
#mocha .test.pending { #mocha .test.pending {
color: #0b97c4; color: var(--mocha-test-pending-color);
} }
#mocha .test.pending::before { #mocha .test.pending::before {
content: '◦'; content: '◦';
color: #0b97c4; color: var(--mocha-test-pending-icon-color);
} }
#mocha .test.fail { #mocha .test.fail {
color: #c00; color: var(--mocha-test-fail-color);
} }
#mocha .test.fail pre { #mocha .test.fail pre {
color: black; color: var(--mocha-test-fail-pre-color);
} }
#mocha .test.fail::before { #mocha .test.fail::before {
@ -127,35 +193,35 @@ body {
display: block; display: block;
float: left; float: left;
margin-right: 5px; margin-right: 5px;
color: #c00; color: var(--mocha-pass-icon-color);
} }
#mocha .test pre.error { #mocha .test pre.error {
color: #c00; color: var(--mocha-test-fail-pre-error-color);
max-height: 300px; max-height: 300px;
overflow: auto; overflow: auto;
} }
#mocha .test .html-error { #mocha .test .html-error {
overflow: auto; overflow: auto;
color: black; color: var(--mocha-test-html-error-color);
display: block; display: block;
float: left; float: left;
clear: left; clear: left;
font: 12px/1.5 monaco, monospace; font: 12px/1.5 monaco, monospace;
margin: 5px; margin: 5px;
padding: 15px; padding: 15px;
border: 1px solid #eee; border: 1px solid var(--mocha-box-shadow-color);
max-width: 85%; /*(1)*/ max-width: 85%; /*(1)*/
max-width: -webkit-calc(100% - 42px); max-width: -webkit-calc(100% - 42px);
max-width: -moz-calc(100% - 42px); max-width: -moz-calc(100% - 42px);
max-width: calc(100% - 42px); /*(2)*/ max-width: calc(100% - 42px); /*(2)*/
max-height: 300px; max-height: 300px;
word-wrap: break-word; word-wrap: break-word;
border-bottom-color: #ddd; border-bottom-color: var(--mocha-box-bottom-color);
-webkit-box-shadow: 0 1px 3px #eee; -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-moz-box-shadow: 0 1px 3px #eee; -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
box-shadow: 0 1px 3px #eee; box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
border-radius: 3px; border-radius: 3px;
@ -187,16 +253,16 @@ body {
font: 12px/1.5 monaco, monospace; font: 12px/1.5 monaco, monospace;
margin: 5px; margin: 5px;
padding: 15px; padding: 15px;
border: 1px solid #eee; border: 1px solid var(--mocha-box-shadow-color);
max-width: 85%; /*(1)*/ max-width: 85%; /*(1)*/
max-width: -webkit-calc(100% - 42px); max-width: -webkit-calc(100% - 42px);
max-width: -moz-calc(100% - 42px); max-width: -moz-calc(100% - 42px);
max-width: calc(100% - 42px); /*(2)*/ max-width: calc(100% - 42px); /*(2)*/
word-wrap: break-word; word-wrap: break-word;
border-bottom-color: #ddd; border-bottom-color: var(--mocha-box-bottom-color);
-webkit-box-shadow: 0 1px 3px #eee; -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-moz-box-shadow: 0 1px 3px #eee; -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
box-shadow: 0 1px 3px #eee; box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
border-radius: 3px; border-radius: 3px;
@ -217,7 +283,7 @@ body {
height: 15px; height: 15px;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
background: #eee; background: var(--mocha-test-replay-bg-color);
font-size: 15px; font-size: 15px;
-webkit-border-radius: 15px; -webkit-border-radius: 15px;
-moz-border-radius: 15px; -moz-border-radius: 15px;
@ -226,11 +292,12 @@ body {
-moz-transition:opacity 200ms; -moz-transition:opacity 200ms;
-o-transition:opacity 200ms; -o-transition:opacity 200ms;
transition: opacity 200ms; transition: opacity 200ms;
opacity: 0.3; opacity: 0.7;
color: #888; color: var(--mocha-test-replay-color);
} }
#mocha .test:hover a.replay { #mocha .test:hover a.replay {
box-shadow: 0 0 1px inset var(--mocha-test-replay-color);
opacity: 1; opacity: 1;
} }
@ -251,7 +318,7 @@ body {
} }
#mocha-error { #mocha-error {
color: #c00; color: var(--mocha-error-color);
font-size: 1.5em; font-size: 1.5em;
font-weight: 100; font-weight: 100;
letter-spacing: 1px; letter-spacing: 1px;
@ -263,7 +330,7 @@ body {
right: 10px; right: 10px;
font-size: 12px; font-size: 12px;
margin: 0; margin: 0;
color: #888; color: var(--mocha-stats-color);
z-index: 1; z-index: 1;
} }
@ -284,7 +351,7 @@ body {
} }
#mocha-stats em { #mocha-stats em {
color: black; color: var(--mocha-stats-em-color);
} }
#mocha-stats a { #mocha-stats a {
@ -293,7 +360,7 @@ body {
} }
#mocha-stats a:hover { #mocha-stats a:hover {
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--mocha-stats-hover-color);
} }
#mocha-stats li { #mocha-stats li {
@ -308,11 +375,11 @@ body {
height: 40px; height: 40px;
} }
#mocha code .comment { color: #ddd; } #mocha code .comment { color: var(--mocha-code-comment); }
#mocha code .init { color: #2f6fad; } #mocha code .init { color: var(--mocha-code-init); }
#mocha code .string { color: #5890ad; } #mocha code .string { color: var(--mocha-code-string); }
#mocha code .keyword { color: #8a6343; } #mocha code .keyword { color: var(--mocha-code-keyword); }
#mocha code .number { color: #2f6fad; } #mocha code .number { color: var(--mocha-code-number); }
@media screen and (max-device-width: 480px) { @media screen and (max-device-width: 480px) {
#mocha { #mocha {

51873
www/static/node_modules/mocha/mocha.js generated vendored

File diff suppressed because one or more lines are too long

View File

@ -1485,6 +1485,9 @@ var WebSocket$1 = (function (EventTarget$$1) {
* registered :-) * registered :-)
*/ */
delay(function delayCallback() { delay(function delayCallback() {
if (this.readyState !== WebSocket.CONNECTING) {
return;
}
if (server) { if (server) {
if ( if (
server.options.verifyClient && server.options.verifyClient &&
@ -1581,8 +1584,9 @@ var WebSocket$1 = (function (EventTarget$$1) {
WebSocket.prototype.send = function send (data) { WebSocket.prototype.send = function send (data) {
var this$1 = this; var this$1 = this;
if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) { if (this.readyState === WebSocket.CONNECTING) {
throw new Error('WebSocket is already in CLOSING or CLOSED state'); // TODO: node>=17 replace with DOMException
throw new Error("Failed to execute 'send' on 'WebSocket': Still in CONNECTING state");
} }
// TODO: handle bufferedAmount // TODO: handle bufferedAmount

View File

@ -51,7 +51,6 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// Try to create EventSources when elements are processed // Try to create EventSources when elements are processed
case "htmx:afterProcessNode": case "htmx:afterProcessNode":
ensureEventSourceOnElement(evt.target); ensureEventSourceOnElement(evt.target);
registerSSE(evt.target);
} }
} }
}); });
@ -112,19 +111,18 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
* @param {HTMLElement} elt * @param {HTMLElement} elt
*/ */
function registerSSE(elt) { function registerSSE(elt) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
// Add message handlers for every `sse-swap` attribute // Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) { queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function (child) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
var sseSwapAttr = api.getAttributeValue(child, "sse-swap"); var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
if (sseSwapAttr) { if (sseSwapAttr) {
@ -145,9 +143,13 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// If the body no longer contains the element, remove the listener // If the body no longer contains the element, remove the listener
if (!api.bodyContains(child)) { if (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener); source.removeEventListener(sseEventName, listener);
return;
} }
// swap the response into the DOM and trigger a notification // swap the response into the DOM and trigger a notification
if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
return;
}
swap(child, event.data); swap(child, event.data);
api.triggerEvent(elt, "htmx:sseMessage", event); api.triggerEvent(elt, "htmx:sseMessage", event);
}; };
@ -160,6 +162,16 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
// Add message handlers for every `hx-trigger="sse:*"` attribute // Add message handlers for every `hx-trigger="sse:*"` attribute
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) { queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(child, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
var sseEventName = api.getAttributeValue(child, "hx-trigger"); var sseEventName = api.getAttributeValue(child, "hx-trigger");
if (sseEventName == null) { if (sseEventName == null) {
@ -220,6 +232,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
ensureEventSource(child, sseURL, retryCount); ensureEventSource(child, sseURL, retryCount);
}); });
registerSSE(elt);
} }
function ensureEventSource(elt, url, retryCount) { function ensureEventSource(elt, url, retryCount) {

View File

@ -21,7 +21,7 @@ export function addClass(elt: Element, clazz: string, delay?: number): void;
* @param element the element to target (defaults to the **body**) * @param element the element to target (defaults to the **body**)
* @returns Promise that resolves immediately if no request is sent, or when the request is complete * @returns Promise that resolves immediately if no request is sent, or when the request is complete
*/ */
export function ajax(verb: string, path: string, element: Element): Promise<void>; export function ajax(verb: string, path: string, element?: Element): Promise<void>;
/** /**
* Issues an htmx-style AJAX request * Issues an htmx-style AJAX request
@ -429,20 +429,57 @@ export interface HtmxConfig {
* If set to true htmx will not update the title of the document when a title tag is found in new content * If set to true htmx will not update the title of the document when a title tag is found in new content
* @default false * @default false
*/ */
ignoreTitle:? boolean; ignoreTitle?: boolean;
/**
* The cache to store evaluated trigger specifications into.
* You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
* @default null
*/
triggerSpecsCache?: {[trigger: string]: HtmxTriggerSpecification[]};
} }
export type HtmxEvent = "htmx:abort"
| "htmx:afterOnLoad"
| "htmx:afterProcessNode"
| "htmx:afterRequest"
| "htmx:afterSettle"
| "htmx:afterSwap"
| "htmx:beforeCleanupElement"
| "htmx:beforeOnLoad"
| "htmx:beforeProcessNode"
| "htmx:beforeRequest"
| "htmx:beforeSwap"
| "htmx:beforeSend"
| "htmx:configRequest"
| "htmx:confirm"
| "htmx:historyCacheError"
| "htmx:historyCacheMiss"
| "htmx:historyCacheMissError"
| "htmx:historyCacheMissLoad"
| "htmx:historyRestore"
| "htmx:load"
| "htmx:noSSESourceError"
| "htmx:onLoadError"
| "htmx:oobAfterSwap"
| "htmx:oobBeforeSwap"
| "htmx:oobErrorNoTarget"
| "htmx:prompt"
| "htmx:pushedIntoHistory"
| "htmx:responseError"
| "htmx:sendError"
| "htmx:sseError"
| "htmx:sseOpen"
| "htmx:swapError"
| "htmx:targetError"
| "htmx:timeout"
| "htmx:validation:validate"
| "htmx:validation:failed"
| "htmx:validation:halted"
| "htmx:xhr:abort"
| "htmx:xhr:loadend"
| "htmx:xhr:loadstart"
| "htmx:xhr:progress"
;
/** /**
* https://htmx.org/extensions/#defining * https://htmx.org/extensions/#defining
*/ */
export interface HtmxExtension { export interface HtmxExtension {
onEvent?: (name: string, evt: CustomEvent) => any; onEvent?: (name: HtmxEvent, evt: CustomEvent) => any;
transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any; transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any;
isInlineSwap?: (swapStyle: any) => any; isInlineSwap?: (swapStyle: any) => any;
handleSwap?: (swapStyle: any, target: any, fragment: any, settleInfo: any) => any; handleSwap?: (swapStyle: any, target: any, fragment: any, settleInfo: any) => any;

View File

@ -89,7 +89,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType; sock.binaryType = htmx.config.wsBinaryType;
return sock; return sock;
}, },
version: "1.9.10" version: "1.9.11"
}; };
/** @type {import("./htmx").HtmxInternalApi} */ /** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, ''); content = content.replace(HEAD_TAG_REGEX, '');
} }
if (htmx.config.useTemplateFragments && partialResponse) { if (htmx.config.useTemplateFragments && partialResponse) {
var documentFragment = parseHTML("<body><template>" + content + "</template></body>", 0); var fragment = parseHTML("<body><template>" + content + "</template></body>", 0);
// @ts-ignore type mismatch between DocumentFragment and Element. // @ts-ignore type mismatch between DocumentFragment and Element.
// TODO: Are these close enough for htmx to use interchangeably? // TODO: Are these close enough for htmx to use interchangeably?
return documentFragment.querySelector('template').content; if (htmx.config.allowScriptTags) {
// if there is a nonce set up, set it on the new script tags
forEach(fragment.querySelectorAll("script"), function (script) {
if (htmx.config.inlineScriptNonce) {
script.nonce = htmx.config.inlineScriptNonce;
}
getInternalData(script).executed = true; // mark as executed due to template insertion semantics
})
} else {
forEach(fragment.querySelectorAll("script"), function (script) {
// remove all script tags if scripts are disabled
removeElement(script);
})
}
return fragment.querySelector('template').content;
} }
switch (startTag) { switch (startTag) {
case "thead": case "thead":
@ -1892,7 +1906,8 @@ return (function () {
} }
function evalScript(script) { function evalScript(script) {
if (htmx.config.allowScriptTags && (script.type === "text/javascript" || script.type === "module" || script.type === "") ) { if (!getInternalData(script).executed && htmx.config.allowScriptTags &&
(script.type === "text/javascript" || script.type === "module" || script.type === "") ) {
var newScript = getDocument().createElement("script"); var newScript = getDocument().createElement("script");
forEach(script.attributes, function (attr) { forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value); newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
} }
function processScripts(elt) { function processScripts(elt) {
if (matches(elt, "script")) { if (!htmx.config.useTemplateFragments) {
evalScript(elt); if (matches(elt, "script")) {
evalScript(elt);
}
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
function shouldProcessHxOn(elt) { function shouldProcessHxOn(elt) {

View File

@ -13,19 +13,19 @@ describe("hx-sse attribute", function() {
}) })
}, },
addEventListener: function(message, l) { addEventListener: function(message, l) {
if (listeners == undefined) { if (listeners[message] === undefined) {
listeners[message] = []; listeners[message] = [];
} }
listeners[message].push(l) listeners[message].push(l)
}, },
sendEvent: function(eventName, data) { sendEvent: function(eventName, data) {
var listeners = listeners[eventName]; var eventListeners = listeners[eventName];
if (listeners) { if (eventListeners) {
listeners.forEach(function(listener) { eventListeners.forEach(function(listener) {
var event = htmx._("makeEvent")(eventName); var event = htmx._("makeEvent")(eventName);
event.data = data; event.data = data;
listener(event); listener(event);
} })
} }
}, },
close: function() { close: function() {
@ -47,6 +47,7 @@ describe("hx-sse attribute", function() {
}); });
afterEach(function() { afterEach(function() {
this.server.restore(); this.server.restore();
this.eventSource = mockEventSource();
clearWorkArea(); clearWorkArea();
}); });

View File

@ -212,4 +212,78 @@ describe("Core htmx Regression Tests", function(){
var input = byId("id_email"); var input = byId("id_email");
input.value.should.equal("supertest@test.com"); input.value.should.equal("supertest@test.com");
}); });
it("script tags only execute once", function(done) {
window.i = 0; // set count to 0
this.server.respondWith('GET', '/test', '<script>console.trace(); window.i++</script>') // increment the count by 1
// make a div w/ a short settle delay to make the problem more obvious
var div = make('<div hx-get="/test" hx-swap="innerHTML settle:5ms"/>');
div.click();
this.server.respond()
setTimeout(function(){
window.i.should.equal(1);
delete window.i;
done();
}, 50)
})
it("script tags only execute once when nested", function(done) {
window.i = 0; // set count to 0
this.server.respondWith('GET', '/test', '<p>foo</p><div><script>console.trace(); window.i++</script></div>') // increment the count by 1
// make a div w/ a short settle delay to make the problem more obvious
var div = make('<div hx-get="/test" hx-swap="innerHTML settle:5ms"/>');
div.click();
this.server.respond()
setTimeout(function(){
window.i.should.equal(1);
delete window.i;
done();
}, 50)
})
it("script tags only execute once using templates", function(done) {
var oldUseTemplateFragmentsValue = htmx.config.useTemplateFragments
htmx.config.useTemplateFragments = true
window.i = 0; // set count to 0
this.server.respondWith('GET', '/test', '<script>console.trace(); window.i++</script>') // increment the count by 1
// make a div w/ a short settle delay to make the problem more obvious
var div = make('<div hx-get="/test" hx-swap="innerHTML settle:5ms"/>');
div.click();
this.server.respond()
setTimeout(function(){
window.i.should.equal(1);
delete window.i;
htmx.config.useTemplateFragments = oldUseTemplateFragmentsValue
done();
}, 50)
})
it("script tags only execute once when nested using templates", function(done) {
var oldUseTemplateFragmentsValue = htmx.config.useTemplateFragments
htmx.config.useTemplateFragments = true
window.i = 0; // set count to 0
this.server.respondWith('GET', '/test', '<p>foo</p><div><script>console.trace(); window.i++</script></div>') // increment the count by 1
// make a div w/ a short settle delay to make the problem more obvious
var div = make('<div hx-get="/test" hx-swap="innerHTML settle:5ms"/>');
div.click();
this.server.respond()
setTimeout(function(){
window.i.should.equal(1);
delete window.i;
htmx.config.useTemplateFragments = oldUseTemplateFragmentsValue
done();
}, 50)
})
}); });

View File

@ -1,5 +1,3 @@
const { assert } = require("chai");
describe("sse extension", function() { describe("sse extension", function() {
function mockEventSource() { function mockEventSource() {
@ -7,6 +5,7 @@ describe("sse extension", function() {
var wasClosed = false; var wasClosed = false;
var url; var url;
var mockEventSource = { var mockEventSource = {
_listeners: listeners,
removeEventListener: function(name, l) { removeEventListener: function(name, l) {
listeners[name] = listeners[name].filter(function(elt, idx, arr) { listeners[name] = listeners[name].filter(function(elt, idx, arr) {
if (arr[idx] === l) { if (arr[idx] === l) {
@ -16,19 +15,19 @@ describe("sse extension", function() {
}) })
}, },
addEventListener: function(message, l) { addEventListener: function(message, l) {
if (listeners == undefined) { if (listeners[message] === undefined) {
listeners[message] = []; listeners[message] = [];
} }
listeners[message].push(l) listeners[message].push(l)
}, },
sendEvent: function(eventName, data) { sendEvent: function(eventName, data) {
var listeners = listeners[eventName]; var eventListeners = listeners[eventName];
if (listeners) { if (eventListeners) {
listeners.forEach(function(listener) { eventListeners.forEach(function(listener) {
var event = htmx._("makeEvent")(eventName); var event = htmx._("makeEvent")(eventName);
event.data = data; event.data = data;
listener(event); listener(event);
} })
} }
}, },
close: function() { close: function() {
@ -56,6 +55,7 @@ describe("sse extension", function() {
}); });
afterEach(function() { afterEach(function() {
this.server.restore(); this.server.restore();
this.eventSource = mockEventSource();
clearWorkArea(); clearWorkArea();
}); });
@ -150,6 +150,16 @@ describe("sse extension", function() {
this.eventSource.wasClosed().should.equal(true) this.eventSource.wasClosed().should.equal(true)
}) })
it('is not listening for events after hx-swap element removed', function() {
var div = make('<div hx-ext="sse" sse-connect="/foo">' +
'<div id="d1" hx-swap="outerHTML" sse-swap="e1">div1</div>' +
'</div>');
this.eventSource._listeners["e1"].should.be.lengthOf(1)
div.removeChild(byId("d1"));
this.eventSource.sendEvent("e1", "Test")
this.eventSource._listeners["e1"].should.be.empty
})
// sse and hx-trigger handlers are distinct // sse and hx-trigger handlers are distinct
it('is closed after removal with no close and activity, sse-swap', function() { it('is closed after removal with no close and activity, sse-swap', function() {
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-ext="sse" sse-connect="/foo">' + var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-ext="sse" sse-connect="/foo">' +
@ -191,7 +201,8 @@ describe("sse extension", function() {
'<div id="d1" sse-connect="/event_stream" sse-swap="e1">div1</div>\n' + '<div id="d1" sse-connect="/event_stream" sse-swap="e1">div1</div>\n' +
'</div>\n' '</div>\n'
) )
this.eventSource.url = "/event_stream" this.eventSource.sendEvent("e1", "Event 1")
byId("d1").innerText.should.equal("Event 1")
}) })
it('only adds sseEventSource to elements with sse-connect', function() { it('only adds sseEventSource to elements with sse-connect', function() {
@ -228,5 +239,63 @@ describe("sse extension", function() {
}) })
it('raises htmx:sseBeforeMessage when receiving message from the server', function () {
var myEventCalled = false;
function handle(evt) {
myEventCalled = true;
}
htmx.on("htmx:sseBeforeMessage", handle)
var div = make('<div hx-ext="sse" sse-connect="/event_stream" sse-swap="e1"></div>');
this.eventSource.sendEvent("e1", '<div id="d1"></div>')
myEventCalled.should.be.true;
htmx.off("htmx:sseBeforeMessage", handle)
})
it('cancels swap when htmx:sseBeforeMessage was cancelled', function () {
var myEventCalled = false;
function handle(evt) {
myEventCalled = true;
evt.preventDefault();
}
htmx.on("htmx:sseBeforeMessage", handle)
var div = make('<div hx-ext="sse" sse-connect="/event_stream" sse-swap="e1"><div id="d1">div1</div></div>');
this.eventSource.sendEvent("e1", '<div id="d1">replaced</div>')
myEventCalled.should.be.true;
byId("d1").innerHTML.should.equal("div1");
htmx.off("htmx:sseBeforeMessage", handle)
})
it('raises htmx:sseMessage when message was completely processed', function () {
var myEventCalled = false;
function handle(evt) {
myEventCalled = true;
}
htmx.on("htmx:sseMessage", handle)
var div = make('<div hx-ext="sse" sse-connect="/event_stream" sse-swap="e1"><div id="d1">div1</div></div>');
this.eventSource.sendEvent("e1", '<div id="d1">replaced</div>')
myEventCalled.should.be.true;
byId("d1").innerHTML.should.equal("replaced");
htmx.off("htmx:sseMessage", handle)
})
}); });

View File

@ -24,7 +24,7 @@
</ul> </ul>
<h2>Manual Tests</h2> <h2>Manual Tests</h2>
<a href="manual">Here</a> <a href="manual/">Here</a>
<h2>Mocha Test Suite</h2> <h2>Mocha Test Suite</h2>
<a href="index.html">[ALL]</a> <a href="index.html">[ALL]</a>

View File

@ -89,7 +89,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType; sock.binaryType = htmx.config.wsBinaryType;
return sock; return sock;
}, },
version: "1.9.10" version: "1.9.11"
}; };
/** @type {import("./htmx").HtmxInternalApi} */ /** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, ''); content = content.replace(HEAD_TAG_REGEX, '');
} }
if (htmx.config.useTemplateFragments && partialResponse) { if (htmx.config.useTemplateFragments && partialResponse) {
var documentFragment = parseHTML("<body><template>" + content + "</template></body>", 0); var fragment = parseHTML("<body><template>" + content + "</template></body>", 0);
// @ts-ignore type mismatch between DocumentFragment and Element. // @ts-ignore type mismatch between DocumentFragment and Element.
// TODO: Are these close enough for htmx to use interchangeably? // TODO: Are these close enough for htmx to use interchangeably?
return documentFragment.querySelector('template').content; if (htmx.config.allowScriptTags) {
// if there is a nonce set up, set it on the new script tags
forEach(fragment.querySelectorAll("script"), function (script) {
if (htmx.config.inlineScriptNonce) {
script.nonce = htmx.config.inlineScriptNonce;
}
getInternalData(script).executed = true; // mark as executed due to template insertion semantics
})
} else {
forEach(fragment.querySelectorAll("script"), function (script) {
// remove all script tags if scripts are disabled
removeElement(script);
})
}
return fragment.querySelector('template').content;
} }
switch (startTag) { switch (startTag) {
case "thead": case "thead":
@ -1892,7 +1906,8 @@ return (function () {
} }
function evalScript(script) { function evalScript(script) {
if (htmx.config.allowScriptTags && (script.type === "text/javascript" || script.type === "module" || script.type === "") ) { if (!getInternalData(script).executed && htmx.config.allowScriptTags &&
(script.type === "text/javascript" || script.type === "module" || script.type === "") ) {
var newScript = getDocument().createElement("script"); var newScript = getDocument().createElement("script");
forEach(script.attributes, function (attr) { forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value); newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
} }
function processScripts(elt) { function processScripts(elt) {
if (matches(elt, "script")) { if (!htmx.config.useTemplateFragments) {
evalScript(elt); if (matches(elt, "script")) {
evalScript(elt);
}
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
forEach(findAll(elt, "script"), function (script) {
evalScript(script);
});
} }
function shouldProcessHxOn(elt) { function shouldProcessHxOn(elt) {