mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-27 04:50:43 +00:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
89dc9bea2e
277
dist/htmx.d.ts → dist/htmx.esm.d.ts
vendored
277
dist/htmx.d.ts → dist/htmx.esm.d.ts
vendored
@ -1,3 +1,145 @@
|
||||
export default htmx;
|
||||
export type HttpVerb = 'get' | 'head' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | 'patch';
|
||||
export type SwapOptions = {
|
||||
select?: string;
|
||||
selectOOB?: string;
|
||||
eventInfo?: any;
|
||||
anchor?: string;
|
||||
contextElement?: Element;
|
||||
afterSwapCallback?: swapCallback;
|
||||
afterSettleCallback?: swapCallback;
|
||||
};
|
||||
export type swapCallback = () => any;
|
||||
export type HtmxSwapStyle = 'innerHTML' | 'outerHTML' | 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend' | 'delete' | 'none' | string;
|
||||
export type HtmxSwapSpecification = {
|
||||
swapStyle: HtmxSwapStyle;
|
||||
swapDelay: number;
|
||||
settleDelay: number;
|
||||
transition?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
head?: string;
|
||||
scroll?: 'top' | 'bottom';
|
||||
scrollTarget?: string;
|
||||
show?: string;
|
||||
showTarget?: string;
|
||||
focusScroll?: boolean;
|
||||
};
|
||||
export type ConditionalFunction = ((this: Node, evt: Event) => boolean) & {
|
||||
source: string;
|
||||
};
|
||||
export type HtmxTriggerSpecification = {
|
||||
trigger: string;
|
||||
pollInterval?: number;
|
||||
eventFilter?: ConditionalFunction;
|
||||
changed?: boolean;
|
||||
once?: boolean;
|
||||
consume?: boolean;
|
||||
delay?: number;
|
||||
from?: string;
|
||||
target?: string;
|
||||
throttle?: number;
|
||||
queue?: string;
|
||||
root?: string;
|
||||
threshold?: string;
|
||||
};
|
||||
export type HtmxElementValidationError = {
|
||||
elt: Element;
|
||||
message: string;
|
||||
validity: ValidityState;
|
||||
};
|
||||
export type HtmxHeaderSpecification = Record<string, string>;
|
||||
export type HtmxAjaxHelperContext = {
|
||||
source?: Element | string;
|
||||
event?: Event;
|
||||
handler?: HtmxAjaxHandler;
|
||||
target?: Element | string;
|
||||
swap?: HtmxSwapStyle;
|
||||
values?: any | FormData;
|
||||
headers?: Record<string, string>;
|
||||
select?: string;
|
||||
};
|
||||
export type HtmxRequestConfig = {
|
||||
boosted: boolean;
|
||||
useUrlParams: boolean;
|
||||
formData: FormData;
|
||||
/**
|
||||
* formData proxy
|
||||
*/
|
||||
parameters: any;
|
||||
unfilteredFormData: FormData;
|
||||
/**
|
||||
* unfilteredFormData proxy
|
||||
*/
|
||||
unfilteredParameters: any;
|
||||
headers: HtmxHeaderSpecification;
|
||||
target: Element;
|
||||
verb: HttpVerb;
|
||||
errors: HtmxElementValidationError[];
|
||||
withCredentials: boolean;
|
||||
timeout: number;
|
||||
path: string;
|
||||
triggeringEvent: Event;
|
||||
};
|
||||
export type HtmxResponseInfo = {
|
||||
xhr: XMLHttpRequest;
|
||||
target: Element;
|
||||
requestConfig: HtmxRequestConfig;
|
||||
etc: HtmxAjaxEtc;
|
||||
boosted: boolean;
|
||||
select: string;
|
||||
pathInfo: {
|
||||
requestPath: string;
|
||||
finalRequestPath: string;
|
||||
responsePath: string | null;
|
||||
anchor: string;
|
||||
};
|
||||
failed?: boolean;
|
||||
successful?: boolean;
|
||||
};
|
||||
export type HtmxAjaxEtc = {
|
||||
returnPromise?: boolean;
|
||||
handler?: HtmxAjaxHandler;
|
||||
select?: string;
|
||||
targetOverride?: Element;
|
||||
swapOverride?: HtmxSwapStyle;
|
||||
headers?: Record<string, string>;
|
||||
values?: any | FormData;
|
||||
credentials?: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
export type HtmxResponseHandlingConfig = {
|
||||
code?: string;
|
||||
swap: boolean;
|
||||
error?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
select?: string;
|
||||
target?: string;
|
||||
swapOverride?: string;
|
||||
event?: string;
|
||||
};
|
||||
export type HtmxBeforeSwapDetails = HtmxResponseInfo & {
|
||||
shouldSwap: boolean;
|
||||
serverResponse: any;
|
||||
isError: boolean;
|
||||
ignoreTitle: boolean;
|
||||
selectOverride: string;
|
||||
};
|
||||
export type HtmxAjaxHandler = (elt: Element, responseInfo: HtmxResponseInfo) => any;
|
||||
export type HtmxSettleTask = (() => void);
|
||||
export type HtmxSettleInfo = {
|
||||
tasks: HtmxSettleTask[];
|
||||
elts: Element[];
|
||||
title?: string;
|
||||
};
|
||||
export type HtmxExtension = {
|
||||
init: (api: any) => void;
|
||||
onEvent: (name: string, event: Event | CustomEvent) => boolean;
|
||||
transformResponse: (text: string, xhr: XMLHttpRequest, elt: Element) => string;
|
||||
isInlineSwap: (swapStyle: HtmxSwapStyle) => boolean;
|
||||
handleSwap: (swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean | Node[];
|
||||
encodeParameters: (xhr: XMLHttpRequest, parameters: FormData, elt: Node) => any | string | null;
|
||||
getSelectors: () => string[] | null;
|
||||
};
|
||||
declare namespace htmx {
|
||||
const onLoad: (callback: (elt: Node) => void) => EventListener;
|
||||
const process: (elt: string | Element) => void;
|
||||
@ -15,7 +157,7 @@ declare namespace htmx {
|
||||
const toggleClass: (elt: string | Element, clazz: string) => void;
|
||||
const takeClass: (elt: string | Node, clazz: string) => void;
|
||||
const swap: (target: string | Element, content: string, swapSpec: HtmxSwapSpecification, swapOptions?: SwapOptions) => void;
|
||||
const defineExtension: (name: string, extension: any) => void;
|
||||
const defineExtension: (name: string, extension: HtmxExtension) => void;
|
||||
const removeExtension: (name: string) => void;
|
||||
const logAll: () => void;
|
||||
const logNone: () => void;
|
||||
@ -60,136 +202,3 @@ declare namespace htmx {
|
||||
const _: (str: string) => any;
|
||||
const version: string;
|
||||
}
|
||||
type HttpVerb = 'get' | 'head' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | 'patch';
|
||||
type SwapOptions = {
|
||||
select?: string;
|
||||
selectOOB?: string;
|
||||
eventInfo?: any;
|
||||
anchor?: string;
|
||||
contextElement?: Element;
|
||||
afterSwapCallback?: swapCallback;
|
||||
afterSettleCallback?: swapCallback;
|
||||
};
|
||||
type swapCallback = () => any;
|
||||
type HtmxSwapStyle = 'innerHTML' | 'outerHTML' | 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend' | 'delete' | 'none' | string;
|
||||
type HtmxSwapSpecification = {
|
||||
swapStyle: HtmxSwapStyle;
|
||||
swapDelay: number;
|
||||
settleDelay: number;
|
||||
transition?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
head?: string;
|
||||
scroll?: 'top' | 'bottom';
|
||||
scrollTarget?: string;
|
||||
show?: string;
|
||||
showTarget?: string;
|
||||
focusScroll?: boolean;
|
||||
};
|
||||
type ConditionalFunction = ((this: Node, evt: Event) => boolean) & {
|
||||
source: string;
|
||||
};
|
||||
type HtmxTriggerSpecification = {
|
||||
trigger: string;
|
||||
pollInterval?: number;
|
||||
eventFilter?: ConditionalFunction;
|
||||
changed?: boolean;
|
||||
once?: boolean;
|
||||
consume?: boolean;
|
||||
delay?: number;
|
||||
from?: string;
|
||||
target?: string;
|
||||
throttle?: number;
|
||||
queue?: string;
|
||||
root?: string;
|
||||
threshold?: string;
|
||||
};
|
||||
type HtmxElementValidationError = {
|
||||
elt: Element;
|
||||
message: string;
|
||||
validity: ValidityState;
|
||||
};
|
||||
type HtmxHeaderSpecification = Record<string, string>;
|
||||
type HtmxAjaxHelperContext = {
|
||||
source?: Element | string;
|
||||
event?: Event;
|
||||
handler?: HtmxAjaxHandler;
|
||||
target?: Element | string;
|
||||
swap?: HtmxSwapStyle;
|
||||
values?: any | FormData;
|
||||
headers?: Record<string, string>;
|
||||
select?: string;
|
||||
};
|
||||
type HtmxRequestConfig = {
|
||||
boosted: boolean;
|
||||
useUrlParams: boolean;
|
||||
formData: FormData;
|
||||
/**
|
||||
* formData proxy
|
||||
*/
|
||||
parameters: any;
|
||||
unfilteredFormData: FormData;
|
||||
/**
|
||||
* unfilteredFormData proxy
|
||||
*/
|
||||
unfilteredParameters: any;
|
||||
headers: HtmxHeaderSpecification;
|
||||
target: Element;
|
||||
verb: HttpVerb;
|
||||
errors: HtmxElementValidationError[];
|
||||
withCredentials: boolean;
|
||||
timeout: number;
|
||||
path: string;
|
||||
triggeringEvent: Event;
|
||||
};
|
||||
type HtmxResponseInfo = {
|
||||
xhr: XMLHttpRequest;
|
||||
target: Element;
|
||||
requestConfig: HtmxRequestConfig;
|
||||
etc: HtmxAjaxEtc;
|
||||
boosted: boolean;
|
||||
select: string;
|
||||
pathInfo: {
|
||||
requestPath: string;
|
||||
finalRequestPath: string;
|
||||
responsePath: string | null;
|
||||
anchor: string;
|
||||
};
|
||||
failed?: boolean;
|
||||
successful?: boolean;
|
||||
};
|
||||
type HtmxAjaxEtc = {
|
||||
returnPromise?: boolean;
|
||||
handler?: HtmxAjaxHandler;
|
||||
select?: string;
|
||||
targetOverride?: Element;
|
||||
swapOverride?: HtmxSwapStyle;
|
||||
headers?: Record<string, string>;
|
||||
values?: any | FormData;
|
||||
credentials?: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
type HtmxResponseHandlingConfig = {
|
||||
code?: string;
|
||||
swap: boolean;
|
||||
error?: boolean;
|
||||
ignoreTitle?: boolean;
|
||||
select?: string;
|
||||
target?: string;
|
||||
swapOverride?: string;
|
||||
event?: string;
|
||||
};
|
||||
type HtmxBeforeSwapDetails = HtmxResponseInfo & {
|
||||
shouldSwap: boolean;
|
||||
serverResponse: any;
|
||||
isError: boolean;
|
||||
ignoreTitle: boolean;
|
||||
selectOverride: string;
|
||||
};
|
||||
type HtmxAjaxHandler = (elt: Element, responseInfo: HtmxResponseInfo) => any;
|
||||
type HtmxSettleTask = (() => void);
|
||||
type HtmxSettleInfo = {
|
||||
tasks: HtmxSettleTask[];
|
||||
elts: Element[];
|
||||
title?: string;
|
||||
};
|
||||
type HtmxExtension = any;
|
@ -29,7 +29,7 @@
|
||||
"description-sections": {
|
||||
"Not inherited": ""
|
||||
},
|
||||
"doc-url": "https://htmx.org/attributes/hx-confirm/"
|
||||
"doc-url": "https://htmx.org/attributes/hx-delete/"
|
||||
},
|
||||
{
|
||||
"name": "hx-disable",
|
||||
|
@ -14,14 +14,14 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"dist/htmx.d.ts",
|
||||
"dist/htmx.esm.d.ts",
|
||||
"dist/*.js",
|
||||
"dist/ext/*.js",
|
||||
"dist/*.js.gz",
|
||||
"editors/jetbrains/htmx.web-types.json"
|
||||
],
|
||||
"main": "dist/htmx.esm.js",
|
||||
"types": "dist/htmx.d.ts",
|
||||
"types": "dist/htmx.esm.d.ts",
|
||||
"unpkg": "dist/htmx.min.js",
|
||||
"web-types": "editors/jetbrains/htmx.web-types.json",
|
||||
"scripts": {
|
||||
@ -29,7 +29,7 @@
|
||||
"lint": "eslint src/htmx.js test/attributes/ test/core/ test/util/",
|
||||
"format": "eslint --fix src/htmx.js test/attributes/ test/core/ test/util/",
|
||||
"types-check": "tsc src/htmx.js --noEmit --checkJs --target es6 --lib dom,dom.iterable",
|
||||
"types-generate": "tsc src/htmx.js --declaration --emitDeclarationOnly --allowJs --outDir dist",
|
||||
"types-generate": "tsc dist/htmx.esm.js --declaration --emitDeclarationOnly --allowJs --outDir dist",
|
||||
"test": "npm run lint && npm run types-check && mocha-chrome test/index.html",
|
||||
"type-declarations": "tsc --project ./jsconfig.json",
|
||||
"ws-tests": "cd ./test/ws-sse && node ./server.js",
|
||||
|
12
src/htmx.d.ts
vendored
12
src/htmx.d.ts
vendored
@ -15,7 +15,7 @@ declare namespace htmx {
|
||||
const toggleClass: (elt: string | Element, clazz: string) => void;
|
||||
const takeClass: (elt: string | Node, clazz: string) => void;
|
||||
const swap: (target: string | Element, content: string, swapSpec: HtmxSwapSpecification, swapOptions?: SwapOptions) => void;
|
||||
const defineExtension: (name: string, extension: any) => void;
|
||||
const defineExtension: (name: string, extension: HtmxExtension) => void;
|
||||
const removeExtension: (name: string) => void;
|
||||
const logAll: () => void;
|
||||
const logNone: () => void;
|
||||
@ -192,4 +192,12 @@ type HtmxSettleInfo = {
|
||||
elts: Element[];
|
||||
title?: string;
|
||||
};
|
||||
type HtmxExtension = any;
|
||||
type HtmxExtension = {
|
||||
init: (api: any) => void;
|
||||
onEvent: (name: string, event: Event | CustomEvent) => boolean;
|
||||
transformResponse: (text: string, xhr: XMLHttpRequest, elt: Element) => string;
|
||||
isInlineSwap: (swapStyle: HtmxSwapStyle) => boolean;
|
||||
handleSwap: (swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean | Node[];
|
||||
encodeParameters: (xhr: XMLHttpRequest, parameters: FormData, elt: Node) => any | string | null;
|
||||
getSelectors: () => string[] | null;
|
||||
};
|
||||
|
22
src/htmx.js
22
src/htmx.js
@ -1753,7 +1753,7 @@ var htmx = (function() {
|
||||
try {
|
||||
const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
|
||||
if (newElements) {
|
||||
if (typeof newElements.length !== 'undefined') {
|
||||
if (Array.isArray(newElements)) {
|
||||
// if handleSwap returns an array (like) of elements, we handle them
|
||||
for (let j = 0; j < newElements.length; j++) {
|
||||
const child = newElements[j]
|
||||
@ -1952,7 +1952,10 @@ var htmx = (function() {
|
||||
for (const eventName in triggers) {
|
||||
if (triggers.hasOwnProperty(eventName)) {
|
||||
let detail = triggers[eventName]
|
||||
if (!isRawObject(detail)) {
|
||||
if (isRawObject(detail)) {
|
||||
// @ts-ignore
|
||||
elt = detail.target !== undefined ? detail.target : elt
|
||||
} else {
|
||||
detail = { value: detail }
|
||||
}
|
||||
triggerEvent(elt, eventName, detail)
|
||||
@ -3388,10 +3391,10 @@ var htmx = (function() {
|
||||
function overrideFormData(receiver, donor) {
|
||||
for (const key of donor.keys()) {
|
||||
receiver.delete(key)
|
||||
donor.getAll(key).forEach(function(value) {
|
||||
receiver.append(key, value)
|
||||
})
|
||||
}
|
||||
donor.forEach(function(value, key) {
|
||||
receiver.append(key, value)
|
||||
})
|
||||
return receiver
|
||||
}
|
||||
|
||||
@ -4014,6 +4017,8 @@ var htmx = (function() {
|
||||
target.delete(name)
|
||||
if (typeof value.forEach === 'function') {
|
||||
value.forEach(function(v) { target.append(name, v) })
|
||||
} else if (typeof value === 'object' && !(value instanceof Blob)) {
|
||||
target.append(name, JSON.stringify(value))
|
||||
} else {
|
||||
target.append(name, value)
|
||||
}
|
||||
@ -5132,12 +5137,13 @@ var htmx = (function() {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
|
||||
* @typedef {Object} HtmxExtension
|
||||
* @see https://htmx.org/extensions/#defining
|
||||
* @property {(api: any) => void} init
|
||||
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
|
||||
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
|
||||
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
|
||||
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap
|
||||
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters
|
||||
* @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap
|
||||
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters
|
||||
* @property {() => string[]|null} getSelectors
|
||||
*/
|
||||
|
@ -147,6 +147,21 @@ describe('Core htmx AJAX headers', function() {
|
||||
invokedEvent.should.equal(true)
|
||||
})
|
||||
|
||||
it('should handle JSON with target array arg HX-Trigger response header properly', function() {
|
||||
this.server.respondWith('GET', '/test', [200, { 'HX-Trigger': '{"foo":{"target":"#testdiv"}}' }, ''])
|
||||
|
||||
var div = make('<div hx-get="/test"></div>')
|
||||
var testdiv = make('<div id="testdiv"></div>')
|
||||
var invokedEvent = false
|
||||
testdiv.addEventListener('foo', function(evt) {
|
||||
invokedEvent = true
|
||||
evt.detail.elt.should.equal(testdiv)
|
||||
})
|
||||
div.click()
|
||||
this.server.respond()
|
||||
invokedEvent.should.equal(true)
|
||||
})
|
||||
|
||||
it('should survive malformed JSON in HX-Trigger response header', function() {
|
||||
this.server.respondWith('GET', '/test', [200, { 'HX-Trigger': '{not: valid}' }, ''])
|
||||
|
||||
|
@ -293,4 +293,23 @@ describe('Core htmx Parameter Handling', function() {
|
||||
this.server.respond()
|
||||
form.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('order of parameters follows order of input elements with POST', function() {
|
||||
this.server.respondWith('POST', '/test', function(xhr) {
|
||||
xhr.requestBody.should.equal('foo=bar&bar=foo&foo=bar&foo2=bar2')
|
||||
xhr.respond(200, {}, 'Clicked!')
|
||||
})
|
||||
|
||||
var form = make('<form hx-post="/test">' +
|
||||
'<input name="foo" value="bar">' +
|
||||
'<input name="bar" value="foo">' +
|
||||
'<input name="foo" value="bar">' +
|
||||
'<input name="foo2" value="bar2">' +
|
||||
'<button id="b1">Click Me!</button>' +
|
||||
'</form>')
|
||||
|
||||
byId('b1').click()
|
||||
this.server.respond()
|
||||
form.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
})
|
||||
|
@ -60,6 +60,12 @@ document.body.addEventListener("showMessage", function(evt){
|
||||
|
||||
Each property of the JSON object on the right hand side will be copied onto the details object for the event.
|
||||
|
||||
### Targetting Other Elements
|
||||
|
||||
You can trigger events on other target elements by adding a `target` argument to the JSON object.
|
||||
|
||||
`HX-Trigger: {"showMessage":{"target" : "#otherElement"}}`
|
||||
|
||||
### Multiple Triggers
|
||||
|
||||
If you wish to invoke multiple events, you can simply add additional properties to the top level JSON
|
||||
|
Loading…
x
Reference in New Issue
Block a user