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.
Files changed (3) hide show
  1. package/README.md +12 -6
  2. package/htmt.js +63 -35
  3. 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
- ```html
14
- <script type="module">
15
- import { htmt } from "./htmt.js";
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">Loading...</span>
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 = () => load(frame);
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
- let load = (frame) => {
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 = (e) => {
62
- let el = e.target,
63
- name = el?.getAttribute("frame") || el?.getAttribute("target"),
64
- iframe = name && document.querySelector(`iframe[name="${name}"]`);
65
- if (iframe) iframe._ = [el, e.submitter ?? el];
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.1",
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
  }