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
case "htmx:afterProcessNode":
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
*/
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
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");
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 (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener);
return;
}
// swap the response into the DOM and trigger a notification
if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
return;
}
swap(child, event.data);
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
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");
if (sseEventName == null) {
@ -220,6 +232,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
ensureEventSource(child, sseURL, retryCount);
});
registerSSE(elt);
}
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**)
* @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
@ -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
* @default false
*/
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[]};
ignoreTitle?: boolean;
}
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
*/
export interface HtmxExtension {
onEvent?: (name: string, evt: CustomEvent) => any;
onEvent?: (name: HtmxEvent, evt: CustomEvent) => any;
transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any;
isInlineSwap?: (swapStyle: 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;
return sock;
},
version: "1.9.10"
version: "1.9.11"
};
/** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, '');
}
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.
// 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) {
case "thead":
@ -1892,7 +1906,8 @@ return (function () {
}
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");
forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
}
function processScripts(elt) {
if (matches(elt, "script")) {
evalScript(elt);
if (!htmx.config.useTemplateFragments) {
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) {

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}'
, className
)
}
}
this.assert(
el.classList.contains(className)
, 'expected ' + elToString(el) + ' to have class #{exp}'
@ -350,7 +350,7 @@
chai.Assertion.addProperty('displayed', function() {
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(
actual !== 'none'

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

@ -14,7 +14,7 @@ var used = [];
* Chai version
*/
exports.version = '4.3.3';
exports.version = '4.3.8';
/*!
* Assertion Error
@ -8608,6 +8608,7 @@ var config = require('../config');
* messages or should be truncated.
*
* @param {Mixed} javascript object to inspect
* @returns {string} stringified object
* @name objDisplay
* @namespace Utils
* @api public
@ -9062,7 +9063,7 @@ var flag = require('./flag');
/**
* ### .test(object, expression)
*
* Test and object for expression.
* Test an object for expression.
*
* @param {Object} object (constructed Assertion)
* @param {Arguments} chai.Assertion.prototype.assert arguments
@ -9250,6 +9251,7 @@ AssertionError.prototype.toJSON = function (stack) {
* MIT Licensed
*/
var getFunctionName = require('get-func-name');
/**
* ### .checkError
*
@ -9329,34 +9331,6 @@ function compatibleMessage(thrown, errMatcher) {
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)
*
@ -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 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.
constructorName = getFunctionName(errorLike).trim() ||
getFunctionName(new errorLike()); // eslint-disable-line new-cap
constructorName = getFunctionName(errorLike);
if (constructorName === '') {
var newConstructorName = getFunctionName(new errorLike()); // eslint-disable-line new-cap
constructorName = newConstructorName || constructorName;
}
}
return constructorName;
@ -9415,7 +9392,7 @@ module.exports = {
getConstructorName: getConstructorName,
};
},{}],35:[function(require,module,exports){
},{"get-func-name":36}],35:[function(require,module,exports){
'use strict';
/* globals Symbol: false, Uint8Array: false, WeakMap: false */
/*!
@ -9810,8 +9787,15 @@ function getEnumerableKeys(target) {
return keys;
}
function getNonEnumerableSymbols(target) {
var keys = Object.getOwnPropertySymbols(target);
function getEnumerableSymbols(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;
}
@ -9850,8 +9834,8 @@ function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
function objectEqual(leftHandOperand, rightHandOperand, options) {
var leftHandKeys = getEnumerableKeys(leftHandOperand);
var rightHandKeys = getEnumerableKeys(rightHandOperand);
var leftHandSymbols = getNonEnumerableSymbols(leftHandOperand);
var rightHandSymbols = getNonEnumerableSymbols(rightHandOperand);
var leftHandSymbols = getEnumerableSymbols(leftHandOperand);
var rightHandSymbols = getEnumerableSymbols(rightHandOperand);
leftHandKeys = leftHandKeys.concat(leftHandSymbols);
rightHandKeys = rightHandKeys.concat(rightHandSymbols);
@ -9927,6 +9911,7 @@ function mapSymbols(arr) {
var toString = Function.prototype.toString;
var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\s\(\/]+)/;
var maxFunctionSourceLength = 512;
function getFuncName(aFunc) {
if (typeof aFunc !== 'function') {
return null;
@ -9934,8 +9919,15 @@ function getFuncName(aFunc) {
var name = '';
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
var match = toString.call(aFunc).match(functionNameMatch);
var match = functionSource.match(functionNameMatch);
if (match) {
name = match[1];
}
@ -10331,9 +10323,15 @@ module.exports = getFuncName;
}
function inspectDate(dateObject, options) {
// If we need to - truncate the time portion, but never the date
var split = dateObject.toJSON().split('T');
var date = split[0];
var stringRepresentation = dateObject.toJSON();
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');
}
@ -10630,7 +10628,32 @@ module.exports = getFuncName;
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 baseTypesMap = {
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.
return inspectObject(value, options);
} // last chance to check if it's an object
if (value === Object(value)) {
return inspectObject(value, options);
} // We have run out of options! Just stringify the value
@ -10775,7 +10803,7 @@ module.exports = getFuncName;
return false;
}
constructorMap.add(constructor, inspector);
constructorMap.set(constructor, inspector);
return true;
}
function registerStringTag(stringTag, inspector) {

View File

@ -1,7 +1,73 @@
@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 {
margin:0;
background-color: var(--mocha-bg-color);
color: var(--mocha-color);
}
#mocha {
@ -69,11 +135,11 @@ body {
}
#mocha .test.pass.medium .duration {
background: #c09853;
background: var(--mocha-pass-mediump-color);
}
#mocha .test.pass.slow .duration {
background: #b94a48;
background: var(--mocha-pass-slow-color);
}
#mocha .test.pass::before {
@ -82,17 +148,17 @@ body {
display: block;
float: left;
margin-right: 5px;
color: #00d6b2;
color: var(--mocha-pass-icon-color);
}
#mocha .test.pass .duration {
font-size: 9px;
margin-left: 5px;
padding: 2px 5px;
color: #fff;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
color: var(--mocha-pass-color);
-webkit-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
-moz-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
@ -105,20 +171,20 @@ body {
}
#mocha .test.pending {
color: #0b97c4;
color: var(--mocha-test-pending-color);
}
#mocha .test.pending::before {
content: '◦';
color: #0b97c4;
color: var(--mocha-test-pending-icon-color);
}
#mocha .test.fail {
color: #c00;
color: var(--mocha-test-fail-color);
}
#mocha .test.fail pre {
color: black;
color: var(--mocha-test-fail-pre-color);
}
#mocha .test.fail::before {
@ -127,35 +193,35 @@ body {
display: block;
float: left;
margin-right: 5px;
color: #c00;
color: var(--mocha-pass-icon-color);
}
#mocha .test pre.error {
color: #c00;
color: var(--mocha-test-fail-pre-error-color);
max-height: 300px;
overflow: auto;
}
#mocha .test .html-error {
overflow: auto;
color: black;
color: var(--mocha-test-html-error-color);
display: block;
float: left;
clear: left;
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
border: 1px solid var(--mocha-box-shadow-color);
max-width: 85%; /*(1)*/
max-width: -webkit-calc(100% - 42px);
max-width: -moz-calc(100% - 42px);
max-width: calc(100% - 42px); /*(2)*/
max-height: 300px;
word-wrap: break-word;
border-bottom-color: #ddd;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-box-shadow: 0 1px 3px #eee;
box-shadow: 0 1px 3px #eee;
border-bottom-color: var(--mocha-box-bottom-color);
-webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
@ -187,16 +253,16 @@ body {
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
border: 1px solid var(--mocha-box-shadow-color);
max-width: 85%; /*(1)*/
max-width: -webkit-calc(100% - 42px);
max-width: -moz-calc(100% - 42px);
max-width: calc(100% - 42px); /*(2)*/
word-wrap: break-word;
border-bottom-color: #ddd;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-box-shadow: 0 1px 3px #eee;
box-shadow: 0 1px 3px #eee;
border-bottom-color: var(--mocha-box-bottom-color);
-webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
@ -217,7 +283,7 @@ body {
height: 15px;
line-height: 15px;
text-align: center;
background: #eee;
background: var(--mocha-test-replay-bg-color);
font-size: 15px;
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
@ -226,11 +292,12 @@ body {
-moz-transition:opacity 200ms;
-o-transition:opacity 200ms;
transition: opacity 200ms;
opacity: 0.3;
color: #888;
opacity: 0.7;
color: var(--mocha-test-replay-color);
}
#mocha .test:hover a.replay {
box-shadow: 0 0 1px inset var(--mocha-test-replay-color);
opacity: 1;
}
@ -251,7 +318,7 @@ body {
}
#mocha-error {
color: #c00;
color: var(--mocha-error-color);
font-size: 1.5em;
font-weight: 100;
letter-spacing: 1px;
@ -263,7 +330,7 @@ body {
right: 10px;
font-size: 12px;
margin: 0;
color: #888;
color: var(--mocha-stats-color);
z-index: 1;
}
@ -284,7 +351,7 @@ body {
}
#mocha-stats em {
color: black;
color: var(--mocha-stats-em-color);
}
#mocha-stats a {
@ -293,7 +360,7 @@ body {
}
#mocha-stats a:hover {
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--mocha-stats-hover-color);
}
#mocha-stats li {
@ -308,11 +375,11 @@ body {
height: 40px;
}
#mocha code .comment { color: #ddd; }
#mocha code .init { color: #2f6fad; }
#mocha code .string { color: #5890ad; }
#mocha code .keyword { color: #8a6343; }
#mocha code .number { color: #2f6fad; }
#mocha code .comment { color: var(--mocha-code-comment); }
#mocha code .init { color: var(--mocha-code-init); }
#mocha code .string { color: var(--mocha-code-string); }
#mocha code .keyword { color: var(--mocha-code-keyword); }
#mocha code .number { color: var(--mocha-code-number); }
@media screen and (max-device-width: 480px) {
#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 :-)
*/
delay(function delayCallback() {
if (this.readyState !== WebSocket.CONNECTING) {
return;
}
if (server) {
if (
server.options.verifyClient &&
@ -1581,8 +1584,9 @@ var WebSocket$1 = (function (EventTarget$$1) {
WebSocket.prototype.send = function send (data) {
var this$1 = this;
if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
throw new Error('WebSocket is already in CLOSING or CLOSED state');
if (this.readyState === WebSocket.CONNECTING) {
// TODO: node>=17 replace with DOMException
throw new Error("Failed to execute 'send' on 'WebSocket': Still in CONNECTING state");
}
// 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
case "htmx:afterProcessNode":
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
*/
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
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");
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 (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener);
return;
}
// swap the response into the DOM and trigger a notification
if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
return;
}
swap(child, event.data);
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
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");
if (sseEventName == null) {
@ -220,6 +232,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
ensureEventSource(child, sseURL, retryCount);
});
registerSSE(elt);
}
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**)
* @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
@ -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
* @default false
*/
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[]};
ignoreTitle?: boolean;
}
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
*/
export interface HtmxExtension {
onEvent?: (name: string, evt: CustomEvent) => any;
onEvent?: (name: HtmxEvent, evt: CustomEvent) => any;
transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any;
isInlineSwap?: (swapStyle: 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;
return sock;
},
version: "1.9.10"
version: "1.9.11"
};
/** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, '');
}
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.
// 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) {
case "thead":
@ -1892,7 +1906,8 @@ return (function () {
}
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");
forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
}
function processScripts(elt) {
if (matches(elt, "script")) {
evalScript(elt);
if (!htmx.config.useTemplateFragments) {
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) {

View File

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

View File

@ -212,4 +212,78 @@ describe("Core htmx Regression Tests", function(){
var input = byId("id_email");
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() {
function mockEventSource() {
@ -7,6 +5,7 @@ describe("sse extension", function() {
var wasClosed = false;
var url;
var mockEventSource = {
_listeners: listeners,
removeEventListener: function(name, l) {
listeners[name] = listeners[name].filter(function(elt, idx, arr) {
if (arr[idx] === l) {
@ -16,19 +15,19 @@ describe("sse extension", function() {
})
},
addEventListener: function(message, l) {
if (listeners == undefined) {
if (listeners[message] === undefined) {
listeners[message] = [];
}
listeners[message].push(l)
},
sendEvent: function(eventName, data) {
var listeners = listeners[eventName];
if (listeners) {
listeners.forEach(function(listener) {
var eventListeners = listeners[eventName];
if (eventListeners) {
eventListeners.forEach(function(listener) {
var event = htmx._("makeEvent")(eventName);
event.data = data;
listener(event);
}
})
}
},
close: function() {
@ -56,6 +55,7 @@ describe("sse extension", function() {
});
afterEach(function() {
this.server.restore();
this.eventSource = mockEventSource();
clearWorkArea();
});
@ -150,6 +150,16 @@ describe("sse extension", function() {
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
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">' +
@ -191,7 +201,8 @@ describe("sse extension", function() {
'<div id="d1" sse-connect="/event_stream" sse-swap="e1">div1</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() {
@ -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>
<h2>Manual Tests</h2>
<a href="manual">Here</a>
<a href="manual/">Here</a>
<h2>Mocha Test Suite</h2>
<a href="index.html">[ALL]</a>

View File

@ -89,7 +89,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.9.10"
version: "1.9.11"
};
/** @type {import("./htmx").HtmxInternalApi} */
@ -309,10 +309,24 @@ return (function () {
content = content.replace(HEAD_TAG_REGEX, '');
}
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.
// 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) {
case "thead":
@ -1892,7 +1906,8 @@ return (function () {
}
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");
forEach(script.attributes, function (attr) {
newScript.setAttribute(attr.name, attr.value);
@ -1918,12 +1933,14 @@ return (function () {
}
function processScripts(elt) {
if (matches(elt, "script")) {
evalScript(elt);
if (!htmx.config.useTemplateFragments) {
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) {