prep beta4

This commit is contained in:
Carson Gross 2024-05-16 16:39:48 -06:00
parent a216e1b799
commit 12675fd1d4
15 changed files with 295 additions and 41 deletions

View File

@ -33,7 +33,7 @@ By removing these arbitrary constraints htmx completes HTML as a
## quick start
```html
<script src="https://unpkg.com/htmx.org@2.0.0-beta3"></script>
<script src="https://unpkg.com/htmx.org@2.0.0-beta4"></script>
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me

41
dist/htmx.amd.js vendored
View File

@ -165,6 +165,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -272,7 +278,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2252,6 +2258,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2274,7 +2287,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2636,8 +2649,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2696,7 +2722,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2709,6 +2735,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4755,6 +4784,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4853,8 +4883,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\

41
dist/htmx.cjs.js vendored
View File

@ -164,6 +164,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -271,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2251,6 +2257,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2273,7 +2286,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2635,8 +2648,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2695,7 +2721,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2708,6 +2734,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4754,6 +4783,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4852,8 +4882,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\

41
dist/htmx.esm.js vendored
View File

@ -164,6 +164,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -271,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2251,6 +2257,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2273,7 +2286,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2635,8 +2648,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2695,7 +2721,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2708,6 +2734,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4754,6 +4783,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4852,8 +4882,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\

41
dist/htmx.js vendored
View File

@ -164,6 +164,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -271,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2251,6 +2257,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2273,7 +2286,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2635,8 +2648,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2695,7 +2721,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2708,6 +2734,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4754,6 +4783,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4852,8 +4882,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\

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

@ -277,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper

View File

@ -60,7 +60,7 @@ By removing these constraints, htmx completes HTML as a [hypertext](https://en.w
<h2>quick start</h2>
```html
<script src="https://unpkg.com/htmx.org@2.0.0-beta3"></script>
<script src="https://unpkg.com/htmx.org@2.0.0-beta4"></script>
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me

View File

@ -119,13 +119,13 @@ The fastest way to get going with htmx is to load it via a CDN. You can simply a
and get going:
```html
<script src="https://unpkg.com/htmx.org@2.0.0-beta3" integrity="sha384-9wQIC/7bdsqgMwBWWcREHyQOU7vU6P9J2tgQq0056HPV9t35Ypm4vPYA6Ud1MgY9" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@2.0.0-beta4" integrity="sha384-dyhvbZOkY/iX5LpSP52hCAEhdXOWGTihIhopb2JLOkmikMvCSrS5YdMWckqsqeS+" crossorigin="anonymous"></script>
```
Unminified version is also available
```html
<script src="https://unpkg.com/htmx.org@2.0.0-beta3/dist/htmx.js" integrity="sha384-lGcccsClz1aGJ47pTvGg8H69LcUEacfYDRwjlmFmRi35GGy8sBegHZaca5O4/Kai" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@2.0.0-beta4/dist/htmx.js" integrity="sha384-5N77nsQeMeg7iHYch5J40eakz39IlEyDhUJ3gn1NKdnjL5mlflCTK3DXnDlDCsSa" crossorigin="anonymous"></script>
```
While the CDN approach is extremely simple, you may want to consider [not using CDNs in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn).

View File

@ -36,6 +36,7 @@ declare namespace htmx {
const allowEval: boolean;
const allowScriptTags: boolean;
const inlineScriptNonce: string;
const inlineStyleNonce: string;
const attributesToSettle: string[];
const withCredentials: boolean;
const timeout: number;
@ -53,6 +54,7 @@ declare namespace htmx {
const triggerSpecsCache: any | null;
const disableInheritance: boolean;
const responseHandling: HtmxResponseHandlingConfig[];
const allowNestedOobSwaps: boolean;
}
const parseInterval: (str: string) => number;
const _: (str: string) => any;

View File

@ -164,6 +164,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -271,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2251,6 +2257,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2273,7 +2286,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2635,8 +2648,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2695,7 +2721,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2708,6 +2734,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4754,6 +4783,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4852,8 +4882,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\

View File

@ -1,8 +1,8 @@
describe('hx-ext attribute', function() {
var ext1Calls, ext2Calls, ext3Calls, ext4Calls
var ext1Calls, ext2Calls, ext3Calls, ext4Calls, ext5Calls
beforeEach(function() {
ext1Calls = ext2Calls = ext3Calls = ext4Calls = 0
ext1Calls = ext2Calls = ext3Calls = ext4Calls = ext5Calls = 0
this.server = makeServer()
clearWorkArea()
htmx.defineExtension('ext-1', {
@ -33,6 +33,14 @@ describe('hx-ext attribute', function() {
}
}
})
htmx.defineExtension('ext-5', {
getSelectors: function() { return ['[foo]'] },
onEvent: function(name, evt) {
if (name === 'htmx:beforeProcessNode' && evt.target.getAttribute('foo')) {
ext5Calls++
}
}
})
})
afterEach(function() {
@ -41,6 +49,8 @@ describe('hx-ext attribute', function() {
htmx.removeExtension('ext-1')
htmx.removeExtension('ext-2')
htmx.removeExtension('ext-3')
htmx.removeExtension('ext-4')
htmx.removeExtension('ext-5')
})
it('A simple extension is invoked properly', function() {
@ -111,11 +121,23 @@ describe('hx-ext attribute', function() {
ext4Calls.should.equal(1)
})
it('A simple extension is invoked properly for elements it specified in getSelectors', function() {
this.server.respondWith('GET', '/test', [200, { 'HX-Trigger': 'namespace:example' }, ''])
var btn = make('<div data-hx-ext="ext-5"><div foo="bar">test</div></div>')
btn.click()
this.server.respond()
ext1Calls.should.equal(0)
ext2Calls.should.equal(0)
ext3Calls.should.equal(0)
ext4Calls.should.equal(0)
ext5Calls.should.equal(1)
})
it('Extensions are ignored properly', function() {
this.server.respondWith('GET', '/test', 'Clicked!')
make('<div id="div-AA" hx-ext="ext-1, ext-2"><button id="btn-AA" hx-get="/test">Click Me!</button>' +
'<div id="div-BB" hx-ext="ignore:ext-1"><button id="btn-BB" hx-get="/test"></div></div>')
make('<div id="div-AA" hx-ext="ext-1,ext-2,ext-5"><button id="btn-AA" hx-get="/test" foo="foo">Click Me!</button>' +
'<div id="div-BB" hx-ext="ignore:ext-1,ignore:ext-5"><button id="btn-BB" hx-get="/test" foo="foo"></button></div></div>')
var btn1 = byId('btn-AA')
var btn2 = byId('btn-BB')
@ -131,5 +153,7 @@ describe('hx-ext attribute', function() {
ext1Calls.should.equal(1)
ext2Calls.should.equal(2)
ext3Calls.should.equal(0)
ext5Calls.should.equal(1)
})
})

View File

@ -120,7 +120,49 @@ describe('security options', function() {
btn.click()
})
it("can't make egress cross site requests when htmx.config.selfRequestsOnly is enabled", function(done) {
it('can disable hx-on on a single elt', function() {
var btn = make("<button hx-disable hx-on:click='window.foo = true'>Foo</button>")
btn.click()
should.equal(window.foo, undefined)
delete window.foo
})
it('can disable hx-on on a parent elt', function() {
var div = make("<div hx-disable><button id='b1' hx-on:click='window.foo = true'>Foo</button></div>")
var btn = byId('b1')
btn.click()
should.equal(window.foo, undefined)
delete window.foo
})
it('can disable hx-on on a single elt dynamically', function() {
var btn = make("<button hx-on:click='window.foo = true'>Foo</button>")
btn.click()
should.equal(window.foo, true)
delete window.foo
btn.setAttribute('hx-disable', '')
btn.click()
should.equal(window.foo, undefined)
delete window.foo
})
it('can disable hx-on on a parent elt dynamically', function() {
var div = make("<div><button id='b1' hx-on:click='window.foo = true'>Foo</button></div>")
var btn = byId('b1')
btn.click()
should.equal(window.foo, true)
delete window.foo
div.setAttribute('hx-disable', '')
btn.click()
should.equal(window.foo, undefined)
delete window.foo
})
it("can't make egress cross site requests when htmx.config.selfRequestsOnly is true", function(done) {
this.timeout(4000)
// should trigger send error, rather than reject
var listener = htmx.on('htmx:invalidPath', function() {

View File

@ -164,6 +164,12 @@ var htmx = (function() {
* @default ''
*/
inlineScriptNonce: '',
/**
* If set, the nonce will be added to inline styles.
* @type string
* @default ''
*/
inlineStyleNonce: '',
/**
* The attributes to settle during the settling phase.
* @type string[]
@ -271,7 +277,7 @@ var htmx = (function() {
parseInterval: null,
/** @type {typeof internalEval} */
_: null,
version: '2.0.0-beta3'
version: '2.0.0-beta4'
}
// Tsc madness part 2
htmx.onLoad = onLoadHelper
@ -2251,6 +2257,13 @@ var htmx = (function() {
getRawAttribute(elt, 'href').indexOf('#') !== 0
}
/**
* @param {Element} elt
*/
function eltIsDisabled(elt) {
return closest(elt, htmx.config.disableSelector)
}
/**
* @param {Element} elt
* @param {HtmxNodeInternalData} nodeData
@ -2273,7 +2286,7 @@ var htmx = (function() {
triggerSpecs.forEach(function(triggerSpec) {
addEventListener(elt, function(node, evt) {
const elt = asElement(node)
if (closest(elt, htmx.config.disableSelector)) {
if (eltIsDisabled(elt)) {
cleanUpElement(elt)
return
}
@ -2635,8 +2648,21 @@ var htmx = (function() {
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
const extensionSelectors = []
for (const e in extensions) {
const extension = extensions[e]
if (extension.getSelectors) {
var selectors = extension.getSelectors()
if (selectors) {
extensionSelectors.push(selectors)
}
}
}
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]' + extensionSelectors.flat().map(s => ', ' + s).join(''))
return results
} else {
return []
@ -2695,7 +2721,7 @@ var htmx = (function() {
}
/**
* @param {EventTarget} elt
* @param {Element} elt
* @param {string} eventName
* @param {string} code
*/
@ -2708,6 +2734,9 @@ var htmx = (function() {
/** @type EventListener */
const listener = function(e) {
maybeEval(elt, function() {
if (eltIsDisabled(elt)) {
return
}
if (!func) {
func = new Function('event', code)
}
@ -4754,6 +4783,7 @@ var htmx = (function() {
function extensionBase() {
return {
init: function(api) { return null },
getSelectors: function() { return null },
onEvent: function(name, evt) { return true },
transformResponse: function(text, xhr, elt) { return text },
isInlineSwap: function(swapStyle) { return false },
@ -4852,8 +4882,9 @@ var htmx = (function() {
function insertIndicatorStyles() {
if (htmx.config.includeIndicatorStyles !== false) {
const nonceAttribute = htmx.config.inlineStyleNonce ? ` nonce="${htmx.config.inlineStyleNonce}"` : ''
getDocument().head.insertAdjacentHTML('beforeend',
'<style>\
'<style' + nonceAttribute + '>\
.' + htmx.config.indicatorClass + '{opacity:0}\
.' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
.' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\