Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Carson Gross 2024-08-05 13:12:22 -06:00
commit 89dc9bea2e
8 changed files with 211 additions and 148 deletions

View File

@ -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;

View File

@ -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",

View File

@ -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
View File

@ -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;
};

View File

@ -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
*/

View File

@ -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}' }, ''])

View File

@ -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!')
})
})

View File

@ -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