| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
(() => { |
| 48 |
"use strict"; |
| 49 |
|
| 50 |
|
| 51 |
const AWAITING = "data-awaiting"; |
| 52 |
|
| 53 |
const AMOUNT = "data-awaiting-amount"; |
| 54 |
|
| 55 |
const SHARE = "--awaiting-share"; |
| 56 |
|
| 57 |
|
| 58 |
* The element a request belongs to, or null when it is not our business. |
| 59 |
* |
| 60 |
* htmx names the element on the event detail. An event for something that |
| 61 |
* never described a wait is left alone rather than given one: the emitter |
| 62 |
* decides which calls are worth marking, and a renderer that marked every |
| 63 |
* request would be overruling it. |
| 64 |
|
| 65 |
const marked = (event) => { |
| 66 |
const element = event.detail?.elt; |
| 67 |
if (!element || typeof element.hasAttribute !== "function") { |
| 68 |
return null; |
| 69 |
} |
| 70 |
return element.hasAttribute(AWAITING) ? element : null; |
| 71 |
}; |
| 72 |
|
| 73 |
|
| 74 |
const begin = (event) => { |
| 75 |
const element = marked(event); |
| 76 |
if (!element) { |
| 77 |
return; |
| 78 |
} |
| 79 |
element.setAttribute("aria-busy", "true"); |
| 80 |
element.style.removeProperty(SHARE); |
| 81 |
}; |
| 82 |
|
| 83 |
|
| 84 |
* Say the wait is over. |
| 85 |
* |
| 86 |
* The share is cleared with it. A control pressed twice would otherwise |
| 87 |
* start its second wait wearing the first one's bar, which is a number |
| 88 |
* about a payload that has already arrived. |
| 89 |
|
| 90 |
const end = (event) => { |
| 91 |
const element = marked(event); |
| 92 |
if (!element) { |
| 93 |
return; |
| 94 |
} |
| 95 |
element.removeAttribute("aria-busy"); |
| 96 |
element.style.removeProperty(SHARE); |
| 97 |
}; |
| 98 |
|
| 99 |
|
| 100 |
* What has landed, over what the description said there was. |
| 101 |
* |
| 102 |
* `event.loaded` when the browser is counting, against the described |
| 103 |
* amount rather than against `event.total`. The two usually agree, and |
| 104 |
* where they do not the description is the one the bar was drawn for: a |
| 105 |
* server that gzips a response reports a total the screen never described. |
| 106 |
* |
| 107 |
* A response with no readable length leaves the share alone, so the trough |
| 108 |
* stays empty rather than jumping to something invented. |
| 109 |
|
| 110 |
const progressed = (event) => { |
| 111 |
const element = marked(event); |
| 112 |
if (!element || element.getAttribute(AWAITING) !== "determinate") { |
| 113 |
return; |
| 114 |
} |
| 115 |
const amount = Number(element.getAttribute(AMOUNT)); |
| 116 |
const loaded = Number(event.detail?.loaded); |
| 117 |
if (!Number.isFinite(amount) || amount <= 0 || !Number.isFinite(loaded)) { |
| 118 |
return; |
| 119 |
} |
| 120 |
element.style.setProperty(SHARE, String(Math.min(loaded / amount, 1))); |
| 121 |
}; |
| 122 |
|
| 123 |
|
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
document.addEventListener("htmx:before:request", begin); |
| 131 |
document.addEventListener("htmx:after:request", end); |
| 132 |
document.addEventListener("htmx:xhr:progress", progressed); |
| 133 |
|
| 134 |
|
| 135 |
|
| 136 |
|
| 137 |
document.addEventListener("htmx:abort", end); |
| 138 |
document.addEventListener("htmx:response:error", end); |
| 139 |
document.addEventListener("htmx:send:error", end); |
| 140 |
})(); |
| 141 |
|