mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-01-25 05:06:13 +00:00
Merge remote-tracking branch 'origin/v2.0v2.0' into v2.0v2.0
This commit is contained in:
@@ -27,8 +27,9 @@
|
||||
"scripts": {
|
||||
"dist": "./scripts/dist.sh",
|
||||
"lint": "eslint src/htmx.js test/attributes/ test/core/ test/util/",
|
||||
"lint-fix": "eslint src/htmx.js test/attributes/ test/core/ test/util/ --fix",
|
||||
"format": "eslint --fix src/htmx.js test/attributes/ test/core/ test/util/",
|
||||
"test": "mocha-chrome test/index.html",
|
||||
"test": "npm run lint && mocha-chrome test/index.html",
|
||||
"test-types": "tsc --project ./jsconfig.json",
|
||||
"ws-tests": "cd ./test/ws-sse && node ./server.js",
|
||||
"www": "bash ./scripts/www.sh"
|
||||
|
||||
16
src/htmx.d.ts
vendored
16
src/htmx.d.ts
vendored
@@ -438,6 +438,22 @@ export interface HtmxConfig {
|
||||
triggerSpecsCache?: {[trigger: string]: HtmxTriggerSpecification[]};
|
||||
}
|
||||
|
||||
type HtmxSwapStyle = "innerHTML" | "outerHTML" | "beforebegin" | "afterbegin" | "beforeend" | "afterend" | "delete" | "none" | string
|
||||
|
||||
export interface HtmxSwapSpecification {
|
||||
swapStyle: HtmxSwapStyle;
|
||||
swapDelay?: number;
|
||||
settleDelay?: number;
|
||||
transition?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
head?: string;
|
||||
scroll?: string;
|
||||
scrollTarget?: string;
|
||||
show?: string;
|
||||
showTarget?: string;
|
||||
focusScroll?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://htmx.org/extensions/#defining
|
||||
*/
|
||||
|
||||
855
src/htmx.js
855
src/htmx.js
File diff suppressed because it is too large
Load Diff
@@ -139,26 +139,26 @@ describe('hx-push-url attribute', function() {
|
||||
|
||||
it('deals with malformed JSON in history cache when saving', function() {
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, 'Invalid JSON')
|
||||
htmx._('saveToHistoryCache')('url', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url', make('<div>'))
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME))
|
||||
cache.length.should.equal(1)
|
||||
})
|
||||
|
||||
it('does not blow out cache when saving a URL twice', function() {
|
||||
htmx._('saveToHistoryCache')('url1', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url2', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url3', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url2', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url1', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url2', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url3', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url2', make('<div>'))
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME))
|
||||
cache.length.should.equal(3)
|
||||
})
|
||||
|
||||
it('history cache is LRU', function() {
|
||||
htmx._('saveToHistoryCache')('url1', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url2', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url3', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url2', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url1', make("<div>"))
|
||||
htmx._('saveToHistoryCache')('url1', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url2', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url3', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url2', make('<div>'))
|
||||
htmx._('saveToHistoryCache')('url1', make('<div>'))
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME))
|
||||
cache.length.should.equal(3)
|
||||
cache[0].url.should.equal('url3')
|
||||
@@ -210,7 +210,7 @@ describe('hx-push-url attribute', function() {
|
||||
}
|
||||
try {
|
||||
localStorage.removeItem('htmx-history-cache')
|
||||
htmx._('saveToHistoryCache')('/dummy', make("<div>" + bigContent + "</div>"), 'Foo', 0)
|
||||
htmx._('saveToHistoryCache')('/dummy', make('<div>' + bigContent + '</div>'), 'Foo', 0)
|
||||
should.equal(localStorage.getItem('htmx-history-cache'), null)
|
||||
} finally {
|
||||
// clear history cache afterwards
|
||||
|
||||
@@ -892,10 +892,10 @@ describe('hx-trigger attribute', function() {
|
||||
var div = make('<div hx-get="/test" hx-trigger="revealed, click" style="position: fixed; top: 1px; left: 1px; border: 3px solid red">foo</div>')
|
||||
div.innerHTML.should.equal('foo')
|
||||
this.server.respond()
|
||||
div.innerHTML.should.equal('Requests: 1');
|
||||
div.click();
|
||||
div.innerHTML.should.equal('Requests: 1')
|
||||
div.click()
|
||||
this.server.respond()
|
||||
div.innerHTML.should.equal('Requests: 2');
|
||||
div.innerHTML.should.equal('Requests: 2')
|
||||
})
|
||||
|
||||
it('revealed doesnt cause other events to trigger', function() {
|
||||
|
||||
@@ -375,4 +375,32 @@ describe('Core htmx API test', function() {
|
||||
htmx.trigger(div, 'myEvent')
|
||||
myEventCalled.should.equal(true)
|
||||
})
|
||||
|
||||
it('swaps content properly (basic)', function() {
|
||||
var output = make('<output id="output"/>')
|
||||
htmx.swap('#output', '<div>Swapped!</div>', { swapStyle: 'innerHTML' })
|
||||
output.innerHTML.should.be.equal('<div>Swapped!</div>')
|
||||
})
|
||||
|
||||
it('swaps content properly (with select)', function() {
|
||||
var output = make('<output id="output"/>')
|
||||
htmx.swap('#output', '<div><p id="select-me">Swapped!</p></div>', { swapStyle: 'innerHTML' }, { select: '#select-me' })
|
||||
output.innerHTML.should.be.equal('<p id="select-me">Swapped!</p>')
|
||||
})
|
||||
|
||||
it('swaps content properly (with oob)', function() {
|
||||
var output = make('<output id="output"/>')
|
||||
var oobDiv = make('<div id="oob"/>')
|
||||
htmx.swap('#output', '<div id="oob" hx-swap-oob="innerHTML">OOB Swapped!</div><div>Swapped!</div>', { swapStyle: 'innerHTML' })
|
||||
output.innerHTML.should.be.equal('<div>Swapped!</div>')
|
||||
oobDiv.innerHTML.should.be.equal('OOB Swapped!')
|
||||
})
|
||||
|
||||
it('swaps content properly (with select oob)', function() {
|
||||
var output = make('<output id="output"/>')
|
||||
var oobDiv = make('<div id="oob"/>')
|
||||
htmx.swap('#output', '<div id="oob">OOB Swapped!</div><div>Swapped!</div>', { swapStyle: 'innerHTML' }, { selectOOB: '#oob:innerHTML' })
|
||||
output.innerHTML.should.be.equal('<div>Swapped!</div>')
|
||||
oobDiv.innerHTML.should.be.equal('OOB Swapped!')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,62 +1,59 @@
|
||||
describe("default extensions behavior", function() {
|
||||
|
||||
var loadCalls, afterSwapCalls, afterSettleCalls;
|
||||
describe('default extensions behavior', function() {
|
||||
var loadCalls, afterSwapCalls, afterSettleCalls
|
||||
|
||||
beforeEach(function() {
|
||||
loadCalls = [];
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
loadCalls = []
|
||||
this.server = makeServer()
|
||||
clearWorkArea()
|
||||
|
||||
htmx.defineExtension("ext-testswap", {
|
||||
htmx.defineExtension('ext-testswap', {
|
||||
onEvent: function(name, evt) {
|
||||
if (name === "htmx:load") {
|
||||
loadCalls.push(evt.detail.elt);
|
||||
if (name === 'htmx:load') {
|
||||
loadCalls.push(evt.detail.elt)
|
||||
}
|
||||
},
|
||||
handleSwap: function(swapStyle, target, fragment, settleInfo) {
|
||||
// simple outerHTML replacement for tests
|
||||
var parentEl = target.parentElement;
|
||||
parentEl.removeChild(target);
|
||||
let arr = [];
|
||||
var parentEl = target.parentElement
|
||||
parentEl.removeChild(target)
|
||||
const arr = []
|
||||
for (const child of fragment.childNodes) {
|
||||
arr.push(parentEl.appendChild(child));
|
||||
arr.push(parentEl.appendChild(child))
|
||||
}
|
||||
return arr; // return the newly added elements
|
||||
return arr // return the newly added elements
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
htmx.removeExtension("ext-testswap");
|
||||
});
|
||||
this.server.restore()
|
||||
clearWorkArea()
|
||||
htmx.removeExtension('ext-testswap')
|
||||
})
|
||||
|
||||
it('handleSwap: afterSwap and afterSettle triggered if extension defined on parent', function() {
|
||||
this.server.respondWith("GET", "/test", '<button>Clicked!</button>');
|
||||
var div = make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>');
|
||||
var btn = div.firstChild;
|
||||
this.server.respondWith('GET', '/test', '<button>Clicked!</button>')
|
||||
var div = make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>')
|
||||
var btn = div.firstChild
|
||||
btn.click()
|
||||
this.server.respond();
|
||||
loadCalls.length.should.equal(1);
|
||||
loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded
|
||||
});
|
||||
this.server.respond()
|
||||
loadCalls.length.should.equal(1)
|
||||
loadCalls[0].textContent.should.equal('Clicked!') // the new button is loaded
|
||||
})
|
||||
|
||||
it('handleSwap: new content is handled by htmx', function() {
|
||||
this.server.respondWith("GET", "/test", '<button id="test-ext-testswap">Clicked!<span hx-get="/test-inner" hx-trigger="load"></span></button>');
|
||||
this.server.respondWith("GET", "/test-inner", 'Loaded!');
|
||||
make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>').querySelector('button').click();
|
||||
this.server.respondWith('GET', '/test', '<button id="test-ext-testswap">Clicked!<span hx-get="/test-inner" hx-trigger="load"></span></button>')
|
||||
this.server.respondWith('GET', '/test-inner', 'Loaded!')
|
||||
make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>').querySelector('button').click()
|
||||
|
||||
this.server.respond(); // call /test via button trigger=click
|
||||
var btn = byId('test-ext-testswap');
|
||||
btn.textContent.should.equal('Clicked!');
|
||||
loadCalls.length.should.equal(1);
|
||||
loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded
|
||||
this.server.respond() // call /test via button trigger=click
|
||||
var btn = byId('test-ext-testswap')
|
||||
btn.textContent.should.equal('Clicked!')
|
||||
loadCalls.length.should.equal(1)
|
||||
loadCalls[0].textContent.should.equal('Clicked!') // the new button is loaded
|
||||
|
||||
this.server.respond(); // call /test-inner via span trigger=load
|
||||
btn.textContent.should.equal("Clicked!Loaded!");
|
||||
loadCalls.length.should.equal(1); // text should not trigger event
|
||||
});
|
||||
|
||||
});
|
||||
this.server.respond() // call /test-inner via span trigger=load
|
||||
btn.textContent.should.equal('Clicked!Loaded!')
|
||||
loadCalls.length.should.equal(1) // text should not trigger event
|
||||
})
|
||||
})
|
||||
|
||||
@@ -448,6 +448,37 @@ Removes the given extension from htmx
|
||||
htmx.removeExtension("my-extension");
|
||||
```
|
||||
|
||||
### Method - `htmx.swap()` {#swap}
|
||||
|
||||
Performs swapping (and settling) of HTML content
|
||||
|
||||
##### Parameters
|
||||
|
||||
* `target` - the HTML element or string selector of swap target
|
||||
* `content` - string representation of content to be swapped
|
||||
* `swapSpec` - swapping specification, representing parameters from `hx-swap`
|
||||
* `swapStyle` (required) - swapping style (`innerHTML`, `outerHTML`, `beforebegin` etc)
|
||||
* `swapDelay`, `settleDelay` (number) - delays before swapping and settling respectively
|
||||
* `transition` (bool) - whether to use HTML transitions for swap
|
||||
* `ignoreTitle` (bool) - disables page title updates
|
||||
* `head` (string) - specifies `head` tag handling strategy (`merge` or `append`). Leave empty to disable head handling
|
||||
* `scroll`, `scrollTarget`, `show`, `showTarget`, `focusScroll` - specifies scroll handling after swap
|
||||
* `swapOptions` - additional *optional* parameters for swapping
|
||||
* `select` - selector for the content to be swapped (equivalent of `hx-select`)
|
||||
* `selectOOB` - selector for the content to be swapped out-of-band (equivalent of `hx-select-oob`)
|
||||
* `eventInfo` - an object to be attached to `htmx:afterSwap` and `htmx:afterSettle` elements
|
||||
* `anchor` - an anchor element that triggered scroll, will be scrolled into view on settle. Provides simple alternative to full scroll handling
|
||||
* `contextElement` - DOM element that serves as context to swapping operation. Currently used to find extensions enabled for specific element
|
||||
* `afterSwapCallback`, `afterSettleCallback` - callback functions called after swap and settle respectively. Take no arguments
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
```js
|
||||
// swap #output element inner HTML with div element with "Swapped!" text
|
||||
htmx.swap("#output", "<div>Swapped!</div>", {swapStyle: 'innerHTML'});
|
||||
```
|
||||
|
||||
### Method - `htmx.takeClass()` {#takeClass}
|
||||
|
||||
Takes the given class from its siblings, so that among its siblings, only the given element will have the class.
|
||||
|
||||
@@ -202,6 +202,7 @@ The table below lists all other attributes available in htmx.
|
||||
| [`htmx.remove()`](@/api.md#remove) | Removes the given element
|
||||
| [`htmx.removeClass()`](@/api.md#removeClass) | Removes a class from the given element
|
||||
| [`htmx.removeExtension()`](@/api.md#removeExtension) | Removes an htmx [extension](@/extensions/_index.md)
|
||||
| [`htmx.swap()`](@/api.md#swap) | Performs swapping (and settling) of HTML content
|
||||
| [`htmx.takeClass()`](@/api.md#takeClass) | Takes a class from other elements for the given element
|
||||
| [`htmx.toggleClass()`](@/api.md#toggleClass) | Toggles a class from the given element
|
||||
| [`htmx.trigger()`](@/api.md#trigger) | Triggers an event on an element
|
||||
|
||||
Reference in New Issue
Block a user