htmt 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/htmt.js +63 -35
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -8,19 +8,23 @@ It's built on web standards: regular `<a>` and `<form>` elements, standard HTML
|
|
|
8
8
|
|
|
9
9
|
## Quick Start
|
|
10
10
|
|
|
11
|
+
### Stackblitz Playground
|
|
12
|
+
|
|
13
|
+
Try it out live on [Stackblitz](https://stackblitz.com/edit/htmt-playground?file=src%2Findex.html)
|
|
14
|
+
|
|
15
|
+
### Manual Setup
|
|
16
|
+
|
|
11
17
|
1. Load the HTMT script as an ES module and initialize it on your document:
|
|
12
18
|
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
htmt(document.body, true);
|
|
17
|
-
</script>
|
|
19
|
+
```js
|
|
20
|
+
import { htmt } from "htmt";
|
|
21
|
+
htmt(document.body, true);
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
2. Add `target` attributes to your links and forms:
|
|
21
25
|
|
|
22
26
|
```html
|
|
23
|
-
<span id="results"
|
|
27
|
+
<span id="results">...</span>
|
|
24
28
|
|
|
25
29
|
<a href="/search?q=example" target="results"> Search </a>
|
|
26
30
|
```
|
|
@@ -112,6 +116,8 @@ Wrap scripts in a `<template>` in the response `<head>` to delay execution until
|
|
|
112
116
|
|
|
113
117
|
HTMT extracts templates, moves their content to the main document, and runs any scripts there.
|
|
114
118
|
|
|
119
|
+
If a template has a `target` attribute, HTMT treats it as an out-of-band (OOB) swap, replacing the specified element in the main document with the contents of the template.
|
|
120
|
+
|
|
115
121
|
### Works Without JavaScript
|
|
116
122
|
|
|
117
123
|
Your links and forms remain fully functional HTML. Without HTMT loaded, users can still click a link with a `target` attribute—it opens the partial response in a new page or tab, just like normal.
|
package/htmt.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {Element} def
|
|
4
|
+
* @param {Element | null} sub
|
|
5
|
+
* @param {string} attr
|
|
6
|
+
* @param {(el: Element) => void} cb
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
1
9
|
let forTargets = (def, sub, attr, cb) =>
|
|
2
10
|
def
|
|
3
11
|
?.getAttribute(attr)
|
|
@@ -12,13 +20,54 @@ let forTargets = (def, sub, attr, cb) =>
|
|
|
12
20
|
if (target) cb(target);
|
|
13
21
|
});
|
|
14
22
|
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {string} name
|
|
26
|
+
*/
|
|
15
27
|
let mkFrame = (name) => {
|
|
16
28
|
let frame = document.createElement("iframe");
|
|
29
|
+
// @ts-expect-error - adding custom property
|
|
17
30
|
frame._ = [];
|
|
18
31
|
frame.name = name;
|
|
19
32
|
frame.hidden = true;
|
|
20
|
-
frame.onload = () =>
|
|
33
|
+
frame.onload = () => {
|
|
34
|
+
let {
|
|
35
|
+
contentDocument: cd,
|
|
36
|
+
name,
|
|
37
|
+
// @ts-expect-error - custom property
|
|
38
|
+
_: [def, sub],
|
|
39
|
+
} = frame;
|
|
40
|
+
// @ts-expect-error - it will be non-null, onload would not have fired otherwise
|
|
41
|
+
if (cd.URL == "about:blank") return;
|
|
42
|
+
let f = document.createDocumentFragment();
|
|
43
|
+
|
|
44
|
+
forTargets(def, sub, "busy", (el) => el.removeAttribute("aria-busy"));
|
|
45
|
+
forTargets(def, sub, "disabled", (el) => {
|
|
46
|
+
if ("disabled" in el) el.disabled = false;
|
|
47
|
+
el.removeAttribute("aria-disabled");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// @ts-expect-error - it will be non-null, onload would not have fired otherwise
|
|
51
|
+
cd.querySelectorAll("head>template").forEach((t) => {
|
|
52
|
+
let target = t.getAttribute("target");
|
|
53
|
+
target
|
|
54
|
+
? // @ts-expect-error - it's a template element
|
|
55
|
+
document.getElementById(target)?.replaceWith(t.content)
|
|
56
|
+
: // @ts-expect-error - it's a template element
|
|
57
|
+
f.append(t.content);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// @ts-expect-error - it will be non-null, onload would not have fired otherwise
|
|
61
|
+
f.append(...cd.body.childNodes);
|
|
62
|
+
|
|
63
|
+
frame.remove();
|
|
64
|
+
mkFrame(name);
|
|
65
|
+
|
|
66
|
+
setTimeout(() => (htmt(f), document.getElementById(name)?.replaceWith(f)));
|
|
67
|
+
};
|
|
68
|
+
|
|
21
69
|
document.body.append(frame);
|
|
70
|
+
// @ts-expect-error - it will be non-null, we just added it to the DOM
|
|
22
71
|
frame.contentWindow.onbeforeunload = (_, [def, sub] = frame._) => {
|
|
23
72
|
forTargets(def, sub, "busy", (el) => el.setAttribute("aria-busy", "true"));
|
|
24
73
|
forTargets(def, sub, "disabled", (el) => {
|
|
@@ -28,42 +77,21 @@ let mkFrame = (name) => {
|
|
|
28
77
|
};
|
|
29
78
|
};
|
|
30
79
|
|
|
31
|
-
|
|
32
|
-
let {
|
|
33
|
-
contentDocument: cd,
|
|
34
|
-
name,
|
|
35
|
-
_: [def, sub],
|
|
36
|
-
} = frame;
|
|
37
|
-
if (cd.URL == "about:blank") return;
|
|
38
|
-
let f = document.createDocumentFragment();
|
|
39
|
-
|
|
40
|
-
forTargets(def, sub, "busy", (el) => el.removeAttribute("aria-busy"));
|
|
41
|
-
forTargets(def, sub, "disabled", (el) => {
|
|
42
|
-
if ("disabled" in el) el.disabled = false;
|
|
43
|
-
el.removeAttribute("aria-disabled");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
cd.querySelectorAll("head>template").forEach((t) => {
|
|
47
|
-
t.id
|
|
48
|
-
? document.getElementById(t.id)?.replaceWith(t.content)
|
|
49
|
-
: f.append(t.content);
|
|
50
|
-
});
|
|
51
|
-
f.append(...cd.body.childNodes);
|
|
52
|
-
|
|
53
|
-
frame.remove();
|
|
54
|
-
mkFrame(name);
|
|
55
|
-
|
|
56
|
-
setTimeout(() => (htmt(f), document.getElementById(name)?.replaceWith(f)));
|
|
57
|
-
};
|
|
58
|
-
|
|
80
|
+
/** @type {import("./htmt.d.ts").htmt} */
|
|
59
81
|
let htmt = (root, install) => {
|
|
60
82
|
if (install) {
|
|
61
|
-
onclick = onsubmit =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
onclick = onsubmit =
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @param {MouseEvent | SubmitEvent} e
|
|
87
|
+
*/
|
|
88
|
+
(e) => {
|
|
89
|
+
let el = /** @type {Element} */ (e.target),
|
|
90
|
+
name = el?.getAttribute("frame") || el?.getAttribute("target"),
|
|
91
|
+
iframe = name && document.querySelector(`iframe[name="${name}"]`);
|
|
92
|
+
// @ts-expect-error - custom property
|
|
93
|
+
if (iframe) iframe._ = [el, e.submitter ?? el];
|
|
94
|
+
};
|
|
67
95
|
}
|
|
68
96
|
root.querySelectorAll("a[target],form[target]").forEach((el) => {
|
|
69
97
|
let name = el?.getAttribute("frame") || el?.getAttribute("target");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmt",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "HyperText Markup Targets",
|
|
6
6
|
"packageManager": "pnpm@10.27.0",
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
"dev": "vite dev --config ./site/vite.config.ts",
|
|
11
11
|
"start": "vite preview --config ./site/vite.config.ts",
|
|
12
12
|
"measure": "echo 'src' && cat ./htmt.js | gzip -c | wc -c && echo 'minified' && cat ./htmt.js | terser | gzip -c | wc -c",
|
|
13
|
-
"test": "playwright test --config=./tests/playwright.config.ts"
|
|
13
|
+
"test": "NODE_ENV=test playwright test --config=./tests/playwright.config.ts"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@playwright/test": "^1.57.0",
|
|
17
|
+
"@tsconfig/node-ts": "^23.6.2",
|
|
18
|
+
"@tsconfig/node24": "^24.0.3",
|
|
17
19
|
"terser": "^5.44.1",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
18
21
|
"vite": "^7.3.1"
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
".": {
|
|
27
30
|
"types": "./htmt.d.ts",
|
|
28
31
|
"default": "./htmt.js"
|
|
29
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
30
34
|
}
|
|
31
35
|
}
|