@loadbare/app 0.4.0
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/LICENSE +201 -0
- package/README.md +95 -0
- package/dist/build/assemble.d.ts +31 -0
- package/dist/build/assemble.d.ts.map +1 -0
- package/dist/build/assemble.js +53 -0
- package/dist/build/cli.d.ts +30 -0
- package/dist/build/cli.d.ts.map +1 -0
- package/dist/build/cli.js +107 -0
- package/dist/build/elements.d.ts +61 -0
- package/dist/build/elements.d.ts.map +1 -0
- package/dist/build/elements.js +158 -0
- package/dist/build/expand.d.ts +45 -0
- package/dist/build/expand.d.ts.map +1 -0
- package/dist/build/expand.js +386 -0
- package/dist/build/format.d.ts +28 -0
- package/dist/build/format.d.ts.map +1 -0
- package/dist/build/format.js +42 -0
- package/dist/build/locations.d.ts +87 -0
- package/dist/build/locations.d.ts.map +1 -0
- package/dist/build/locations.js +173 -0
- package/dist/build/package-root.d.ts +9 -0
- package/dist/build/package-root.d.ts.map +1 -0
- package/dist/build/package-root.js +24 -0
- package/dist/build/pages.d.ts +25 -0
- package/dist/build/pages.d.ts.map +1 -0
- package/dist/build/pages.js +54 -0
- package/dist/build/styles.d.ts +13 -0
- package/dist/build/styles.d.ts.map +1 -0
- package/dist/build/styles.js +18 -0
- package/dist/client.js +522 -0
- package/dist/core/lb-constants.d.ts +23 -0
- package/dist/core/lb-constants.d.ts.map +1 -0
- package/dist/core/lb-constants.js +95 -0
- package/dist/core/lb-types.d.ts +88 -0
- package/dist/core/lb-types.d.ts.map +1 -0
- package/dist/core/lb-types.js +5 -0
- package/dist/demo-static/src/widgets/app-box.d.ts +15 -0
- package/dist/demo-static/src/widgets/app-box.d.ts.map +1 -0
- package/dist/demo-static/src/widgets/app-box.js +19 -0
- package/dist/hub/lb-apply.d.ts +13 -0
- package/dist/hub/lb-apply.d.ts.map +1 -0
- package/dist/hub/lb-apply.js +77 -0
- package/dist/hub/lb-hub.d.ts +2 -0
- package/dist/hub/lb-hub.d.ts.map +1 -0
- package/dist/hub/lb-hub.js +242 -0
- package/dist/hub/lb-rows.d.ts +18 -0
- package/dist/hub/lb-rows.d.ts.map +1 -0
- package/dist/hub/lb-rows.js +106 -0
- package/dist/server/lb-express.d.ts +28 -0
- package/dist/server/lb-express.d.ts.map +1 -0
- package/dist/server/lb-express.js +77 -0
- package/dist/server/lb-server.d.ts +174 -0
- package/dist/server/lb-server.d.ts.map +1 -0
- package/dist/server/lb-server.js +79 -0
- package/dist/tests/assemble.test.d.ts +8 -0
- package/dist/tests/assemble.test.d.ts.map +1 -0
- package/dist/tests/assemble.test.js +51 -0
- package/dist/tests/elements.test.d.ts +8 -0
- package/dist/tests/elements.test.d.ts.map +1 -0
- package/dist/tests/elements.test.js +111 -0
- package/dist/tests/expand.test.d.ts +10 -0
- package/dist/tests/expand.test.d.ts.map +1 -0
- package/dist/tests/expand.test.js +226 -0
- package/dist/tests/fixtures/elements/collision/elements.d.ts +5 -0
- package/dist/tests/fixtures/elements/collision/elements.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/elements.js +3 -0
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.d.ts +2 -0
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.js +1 -0
- package/dist/tests/fixtures/elements/local/widgets/app-box.d.ts +2 -0
- package/dist/tests/fixtures/elements/local/widgets/app-box.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/local/widgets/app-box.js +1 -0
- package/dist/tests/fixtures/elements/manifest/elements.d.ts +5 -0
- package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest/elements.js +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +5 -0
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +5 -0
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +3 -0
- package/dist/tests/golden.test.d.ts +19 -0
- package/dist/tests/golden.test.d.ts.map +1 -0
- package/dist/tests/golden.test.js +60 -0
- package/dist/tests/helpers/console.d.ts +20 -0
- package/dist/tests/helpers/console.d.ts.map +1 -0
- package/dist/tests/helpers/console.js +28 -0
- package/dist/tests/helpers/dom.d.ts +18 -0
- package/dist/tests/helpers/dom.d.ts.map +1 -0
- package/dist/tests/helpers/dom.js +22 -0
- package/dist/tests/helpers/window.d.ts +43 -0
- package/dist/tests/helpers/window.d.ts.map +1 -0
- package/dist/tests/helpers/window.js +78 -0
- package/dist/tests/lb-apply.test.d.ts +8 -0
- package/dist/tests/lb-apply.test.d.ts.map +1 -0
- package/dist/tests/lb-apply.test.js +153 -0
- package/dist/tests/lb-express.test.d.ts +14 -0
- package/dist/tests/lb-express.test.d.ts.map +1 -0
- package/dist/tests/lb-express.test.js +238 -0
- package/dist/tests/lb-input.test.d.ts +9 -0
- package/dist/tests/lb-input.test.d.ts.map +1 -0
- package/dist/tests/lb-input.test.js +78 -0
- package/dist/tests/lb-list.test.d.ts +12 -0
- package/dist/tests/lb-list.test.d.ts.map +1 -0
- package/dist/tests/lb-list.test.js +44 -0
- package/dist/tests/lb-options.test.d.ts +10 -0
- package/dist/tests/lb-options.test.d.ts.map +1 -0
- package/dist/tests/lb-options.test.js +121 -0
- package/dist/tests/lb-picker.test.d.ts +14 -0
- package/dist/tests/lb-picker.test.d.ts.map +1 -0
- package/dist/tests/lb-picker.test.js +59 -0
- package/dist/tests/lb-rows.test.d.ts +12 -0
- package/dist/tests/lb-rows.test.d.ts.map +1 -0
- package/dist/tests/lb-rows.test.js +336 -0
- package/dist/tests/lb-select.test.d.ts +9 -0
- package/dist/tests/lb-select.test.d.ts.map +1 -0
- package/dist/tests/lb-select.test.js +71 -0
- package/dist/tests/lb-server.test.d.ts +9 -0
- package/dist/tests/lb-server.test.d.ts.map +1 -0
- package/dist/tests/lb-server.test.js +495 -0
- package/dist/tests/lb-table.test.d.ts +15 -0
- package/dist/tests/lb-table.test.d.ts.map +1 -0
- package/dist/tests/lb-table.test.js +205 -0
- package/dist/tests/pages.test.d.ts +6 -0
- package/dist/tests/pages.test.d.ts.map +1 -0
- package/dist/tests/pages.test.js +98 -0
- package/dist/tests/styles.test.d.ts +7 -0
- package/dist/tests/styles.test.d.ts.map +1 -0
- package/dist/tests/styles.test.js +73 -0
- package/dist/widgets/index.d.ts +7 -0
- package/dist/widgets/index.d.ts.map +1 -0
- package/dist/widgets/index.js +6 -0
- package/dist/widgets/lb-input.d.ts +2 -0
- package/dist/widgets/lb-input.d.ts.map +1 -0
- package/dist/widgets/lb-input.js +48 -0
- package/dist/widgets/lb-list.d.ts +2 -0
- package/dist/widgets/lb-list.d.ts.map +1 -0
- package/dist/widgets/lb-list.js +17 -0
- package/dist/widgets/lb-options.d.ts +26 -0
- package/dist/widgets/lb-options.d.ts.map +1 -0
- package/dist/widgets/lb-options.js +72 -0
- package/dist/widgets/lb-picker.d.ts +2 -0
- package/dist/widgets/lb-picker.d.ts.map +1 -0
- package/dist/widgets/lb-picker.js +25 -0
- package/dist/widgets/lb-select.d.ts +2 -0
- package/dist/widgets/lb-select.d.ts.map +1 -0
- package/dist/widgets/lb-select.js +43 -0
- package/dist/widgets/lb-table.d.ts +2 -0
- package/dist/widgets/lb-table.d.ts.map +1 -0
- package/dist/widgets/lb-table.js +113 -0
- package/docs/application-chrome.md +36 -0
- package/docs/building-html-pages.md +130 -0
- package/docs/getting-started.md +120 -0
- package/docs/guide.md +1164 -0
- package/docs/hosting.md +218 -0
- package/docs/latent-risks.md +20 -0
- package/docs/theory.md +226 -0
- package/package.json +85 -0
- package/widgets/index.ts +6 -0
- package/widgets/lb-input.html +1 -0
- package/widgets/lb-input.ts +64 -0
- package/widgets/lb-list.html +1 -0
- package/widgets/lb-list.ts +21 -0
- package/widgets/lb-options.html +4 -0
- package/widgets/lb-options.ts +88 -0
- package/widgets/lb-picker.html +7 -0
- package/widgets/lb-picker.ts +27 -0
- package/widgets/lb-select.html +4 -0
- package/widgets/lb-select.ts +55 -0
- package/widgets/lb-table.html +8 -0
- package/widgets/lb-table.ts +126 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import { ATTR_ACTION, ATTR_VALUE, LB_EVENT_NAME, } from "../core/lb-constants";
|
|
3
|
+
/**
|
|
4
|
+
* A leaf cell wrapping a <select>. It receives its value like any other cell,
|
|
5
|
+
* and on change it sends the action the page declared for it — the request
|
|
6
|
+
* vocabulary and the addressing vocabulary running in both directions on one
|
|
7
|
+
* widget.
|
|
8
|
+
*
|
|
9
|
+
* The choice is the interaction, so this is the case where an action carries
|
|
10
|
+
* a value. The server still decides what that choice means.
|
|
11
|
+
*/
|
|
12
|
+
class LbSelect extends HTMLElement {
|
|
13
|
+
static observedAttributes = [ATTR_VALUE];
|
|
14
|
+
attributeChangedCallback(_name, _old, value) {
|
|
15
|
+
const select = this.querySelector("select");
|
|
16
|
+
if (!select) {
|
|
17
|
+
console.error(`lb-select: no <select> to receive the value`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
select.value = value;
|
|
21
|
+
}
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
this.addEventListener("change", () => {
|
|
24
|
+
const select = this.querySelector("select");
|
|
25
|
+
if (!select) {
|
|
26
|
+
console.error(`lb-select: change with no <select>, ignoring`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const name = this.getAttribute(ATTR_ACTION);
|
|
30
|
+
if (!name) {
|
|
31
|
+
console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const detail = {
|
|
35
|
+
op: "action",
|
|
36
|
+
name,
|
|
37
|
+
value: select.value,
|
|
38
|
+
};
|
|
39
|
+
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
customElements.define("lb-select", LbSelect);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-table.d.ts","sourceRoot":"","sources":["../../widgets/lb-table.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import { ATTR_CELL, ATTR_GROUP, ATTR_KEY, ATTR_SORT, } from "../core/lb-constants";
|
|
3
|
+
import { applyRows } from "../hub/lb-rows";
|
|
4
|
+
/**
|
|
5
|
+
* A table that owns its own scaffolding, and the case that shows what a list
|
|
6
|
+
* widget is for once the repeater exists.
|
|
7
|
+
*
|
|
8
|
+
* `lb-list` goes around a table the page author wrote. This one supplies
|
|
9
|
+
* the table, and the page writes what only the page knows: the heading row
|
|
10
|
+
* and the row template. That division is forced rather than chosen — a
|
|
11
|
+
* `<thead>` written inside a custom element is not inside a table, so the
|
|
12
|
+
* parser drops the tag and keeps the text. A `<template>` survives anywhere,
|
|
13
|
+
* so the heading row arrives wrapped and expansion unwraps it into the
|
|
14
|
+
* `lb-template` destination beside the slot.
|
|
15
|
+
*
|
|
16
|
+
* What it adds at run time is placement, and only placement. `lb-sort` names
|
|
17
|
+
* the column rows are ordered by; `lb-group` names the column they are
|
|
18
|
+
* sectioned by, and is the same attribute a grouped `<select>` reads, because
|
|
19
|
+
* a table with section headings and an `<optgroup>` are asking the same thing
|
|
20
|
+
* of the same data.
|
|
21
|
+
*
|
|
22
|
+
* The `foot` destination is the head's argument a second time and needs no
|
|
23
|
+
* code here. A `<tfoot>` is a place on the page, not a row the hub delivers,
|
|
24
|
+
* so what lands in it is an ordinary scope carrying its own `lb-query` — a
|
|
25
|
+
* grand total is a second projection of the same table, and the hub resolves
|
|
26
|
+
* it by name like any other. See demo/pages/ledger.html.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The heading row of a section, carrying the value it stands for.
|
|
30
|
+
*
|
|
31
|
+
* The widget's own scaffolding, so it is HTML's namespace rather than
|
|
32
|
+
* Loadbare App's: it is derived from rows the hub knows and is invisible to the hub
|
|
33
|
+
* itself, exactly as the `<optgroup>` in `lb-options` is. Nothing
|
|
34
|
+
* addresses it, so it carries no `lb-key` and no name from the vocabulary.
|
|
35
|
+
*/
|
|
36
|
+
const GROUP_ROW = "data-group";
|
|
37
|
+
class LbTable extends HTMLElement {
|
|
38
|
+
acceptRows(result) {
|
|
39
|
+
applyRows(this, result, this.place);
|
|
40
|
+
// A section is derived, so it goes when its last row does.
|
|
41
|
+
for (const heading of this.querySelectorAll(`tr[${GROUP_ROW}]`)) {
|
|
42
|
+
if (this.section(heading).rows.length === 0)
|
|
43
|
+
heading.remove();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Group first, then sort within the group.
|
|
48
|
+
*
|
|
49
|
+
* Inserting a row before the first one that sorts after it keeps the
|
|
50
|
+
* section ordered, whatever else is in it — which is what lets a whole set
|
|
51
|
+
* and a single patched row take the same path.
|
|
52
|
+
*/
|
|
53
|
+
place = (row, tuple, template) => {
|
|
54
|
+
const groupCell = template.getAttribute(ATTR_GROUP);
|
|
55
|
+
const sortCell = template.getAttribute(ATTR_SORT);
|
|
56
|
+
const heading = groupCell
|
|
57
|
+
? this.headingFor(template, tuple[groupCell] ?? "")
|
|
58
|
+
: null;
|
|
59
|
+
const { rows, end } = this.section(heading);
|
|
60
|
+
const value = sortCell ? (tuple[sortCell] ?? "") : "";
|
|
61
|
+
const after = sortCell
|
|
62
|
+
? rows.find((other) => other !== row && cellOf(other, sortCell) > value)
|
|
63
|
+
: undefined;
|
|
64
|
+
template.parentElement.insertBefore(row, after ?? end);
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The rows of one section, and the node it ends at.
|
|
68
|
+
*
|
|
69
|
+
* A section runs from its heading to the next heading, or to the template
|
|
70
|
+
* if there is none after it. Without grouping there is one section and it
|
|
71
|
+
* is the whole body — the heading row the page wrote is skipped along the
|
|
72
|
+
* way, because it carries no key and is therefore not a row.
|
|
73
|
+
*/
|
|
74
|
+
section(heading) {
|
|
75
|
+
const template = this.querySelector("template");
|
|
76
|
+
const rows = [];
|
|
77
|
+
let node = heading
|
|
78
|
+
? heading.nextElementSibling
|
|
79
|
+
: template.parentElement.firstElementChild;
|
|
80
|
+
while (node && node !== template && !node.hasAttribute(GROUP_ROW)) {
|
|
81
|
+
if (node.hasAttribute(ATTR_KEY))
|
|
82
|
+
rows.push(node);
|
|
83
|
+
node = node.nextElementSibling;
|
|
84
|
+
}
|
|
85
|
+
return { rows, end: node ?? template };
|
|
86
|
+
}
|
|
87
|
+
/** The section with this label, or a new one in front of the template. */
|
|
88
|
+
headingFor(template, label) {
|
|
89
|
+
const body = template.parentElement;
|
|
90
|
+
for (const heading of body.querySelectorAll(`tr[${GROUP_ROW}]`)) {
|
|
91
|
+
if (heading.getAttribute(GROUP_ROW) === label)
|
|
92
|
+
return heading;
|
|
93
|
+
}
|
|
94
|
+
const heading = document.createElement("tr");
|
|
95
|
+
heading.setAttribute(GROUP_ROW, label);
|
|
96
|
+
const cell = document.createElement("th");
|
|
97
|
+
cell.setAttribute("scope", "rowgroup");
|
|
98
|
+
// How wide the table is, from the row the page author wrote. Nothing
|
|
99
|
+
// else knows, and nothing had to be told twice.
|
|
100
|
+
cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;
|
|
101
|
+
cell.textContent = label;
|
|
102
|
+
heading.append(cell);
|
|
103
|
+
body.insertBefore(heading, template);
|
|
104
|
+
return heading;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** What a live row is showing in one column. */
|
|
108
|
+
function cellOf(row, cell) {
|
|
109
|
+
const selector = `[${ATTR_CELL}="${cell}"]`;
|
|
110
|
+
const el = row.matches(selector) ? row : row.querySelector(selector);
|
|
111
|
+
return el?.textContent ?? "";
|
|
112
|
+
}
|
|
113
|
+
customElements.define("lb-table", LbTable);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Application Chrome
|
|
2
|
+
|
|
3
|
+
The application Chrome - banner, nav, main and footer, is static HTML.
|
|
4
|
+
|
|
5
|
+
It is found by name, not by location: it can be placed anywhere in the tree
|
|
6
|
+
the builder scans, as any file ending `.chrome.html` — there must be exactly
|
|
7
|
+
one. By convention we name it `src/chrome.chrome.html`.
|
|
8
|
+
|
|
9
|
+
In our docs we consider chrome to be invariant across pages. This is
|
|
10
|
+
not a strict technical requirement, as loadbare can update anything,
|
|
11
|
+
but we do not provide examples or tests of dynamic chrome.
|
|
12
|
+
|
|
13
|
+
The minimum requirements for the chrome file are:
|
|
14
|
+
|
|
15
|
+
- the `<lb-hub>` element as first element below `<body>`
|
|
16
|
+
- an empty `<main></main>` element. Its content is populated by
|
|
17
|
+
the hub from the application pages.
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<!doctype html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="utf-8" />
|
|
24
|
+
<title><!-- content goes here --></title>
|
|
25
|
+
<script src="/client.js" defer></script>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<lb-hub>
|
|
29
|
+
<header><!-- content goes here --></header>
|
|
30
|
+
<nav><!-- content goes here --></nav>
|
|
31
|
+
<main></main>
|
|
32
|
+
<footer><!-- content goes here --></footer>
|
|
33
|
+
</lb-hub>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
36
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Building HTML Pages
|
|
2
|
+
|
|
3
|
+
A page is authored as one HTML file. It may contain widget tags. Expansion
|
|
4
|
+
turns each one that has a definition into the tree that definition stands
|
|
5
|
+
for. Expansion runs at build time, before there is a request, and before
|
|
6
|
+
there is any data.
|
|
7
|
+
|
|
8
|
+
`LB-*` is the framework's reserved namespace — Loadbare App's own widgets, and
|
|
9
|
+
the convention its docs use. An application's own widgets should use their
|
|
10
|
+
own prefix instead (`app-nav`, not `lb-nav`); a tag outside the `LB-*`
|
|
11
|
+
namespace expands exactly the same way when it has a definition, but is not
|
|
12
|
+
required to have one — with none, it is assumed to be a plain custom element,
|
|
13
|
+
registered by script alone (see build/elements.ts).
|
|
14
|
+
|
|
15
|
+
## Definitions
|
|
16
|
+
|
|
17
|
+
A definition is one HTML file per tag. The file name gives the tag:
|
|
18
|
+
`lb-options.html` defines `LB-OPTIONS`; `app-nav.html` defines `APP-NAV`.
|
|
19
|
+
|
|
20
|
+
Loadbare App's own widgets live in this package's own `widgets/`, read by
|
|
21
|
+
`loadDefinitions(dirs)`, which takes a list of directories in order — a later
|
|
22
|
+
one overrides an earlier one for the same tag name. An application's own
|
|
23
|
+
definitions are not confined to a directory at all: they are found by
|
|
24
|
+
filename anywhere in its source tree (see `docs/getting-started.md`) and
|
|
25
|
+
overlaid on the built-in set with `addDefinitions`, the same override an
|
|
26
|
+
application directory would give a built-in widget.
|
|
27
|
+
|
|
28
|
+
The full set of definitions is checked for a cycle in which definitions
|
|
29
|
+
reference each other by tag, and throws if one exists.
|
|
30
|
+
|
|
31
|
+
## Expanding a page
|
|
32
|
+
|
|
33
|
+
`expand(source, defs)` walks the authored tree and replaces every widget tag
|
|
34
|
+
that has a matching entry in `defs` with the tree that entry produces. A tag
|
|
35
|
+
in the `LB-*` namespace is required to have one — an unresolved tag there
|
|
36
|
+
throws. Outside that namespace, a tag with no definition is left exactly as
|
|
37
|
+
authored.
|
|
38
|
+
|
|
39
|
+
A definition may itself use other widget tags. Expansion resolves those the
|
|
40
|
+
same way, as deep as the definitions go.
|
|
41
|
+
|
|
42
|
+
## Parameters
|
|
43
|
+
|
|
44
|
+
A definition may declare a placeholder, written `{{name}}`, as the entire
|
|
45
|
+
value of an attribute or the entire text of a text node.
|
|
46
|
+
|
|
47
|
+
An authored tag supplies a placeholder's value with an attribute named
|
|
48
|
+
`exp-<name>`. `exp-label="Member:"` supplies `{{label}}`.
|
|
49
|
+
|
|
50
|
+
HTML lowercases attribute names, so a placeholder name may not contain an
|
|
51
|
+
uppercase letter — `loadDefinitions` rejects a definition that declares one.
|
|
52
|
+
|
|
53
|
+
Supplying `exp-x` on a tag whose definition has no `{{x}}` is an error.
|
|
54
|
+
|
|
55
|
+
If a placeholder has no value supplied, the attribute it fills is dropped
|
|
56
|
+
rather than emitted empty; a text placeholder becomes empty text.
|
|
57
|
+
|
|
58
|
+
## The slot
|
|
59
|
+
|
|
60
|
+
A definition may mark one element `lb-slot`. The authored tag's child nodes
|
|
61
|
+
move into that element, replacing it. A definition with no `lb-slot` rejects
|
|
62
|
+
any non-whitespace content written inside the authored tag.
|
|
63
|
+
|
|
64
|
+
## Named template destinations
|
|
65
|
+
|
|
66
|
+
A definition may mark more than one element `lb-template="<name>"`, each a
|
|
67
|
+
destination. An authored tag supplies content for a destination by writing a
|
|
68
|
+
`<template lb-template="<name>">` among its children; expansion moves the
|
|
69
|
+
template's content into the destination and discards the wrapping `<template>`
|
|
70
|
+
tag. A destination with no matching template is left as the definition wrote
|
|
71
|
+
it.
|
|
72
|
+
|
|
73
|
+
Two templates naming the same destination, or a template naming a destination
|
|
74
|
+
the definition does not declare, is an error.
|
|
75
|
+
|
|
76
|
+
A definition applies named destinations before the slot: content addressed to
|
|
77
|
+
a destination by name does not also count as slot content.
|
|
78
|
+
|
|
79
|
+
### Example
|
|
80
|
+
|
|
81
|
+
`widgets/lb-table.html`:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<table>
|
|
85
|
+
<caption>
|
|
86
|
+
{{caption}}
|
|
87
|
+
</caption>
|
|
88
|
+
<thead lb-template="head"></thead>
|
|
89
|
+
<tbody lb-slot></tbody>
|
|
90
|
+
<tfoot lb-template="foot"></tfoot>
|
|
91
|
+
</table>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Authored in a page:
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<lb-table exp-caption="Everyone, by team">
|
|
98
|
+
<template lb-template="head">
|
|
99
|
+
<tr>
|
|
100
|
+
<th>Name</th>
|
|
101
|
+
<th>Role</th>
|
|
102
|
+
</tr>
|
|
103
|
+
</template>
|
|
104
|
+
<tr>
|
|
105
|
+
<td>Row content for the slot</td>
|
|
106
|
+
</tr>
|
|
107
|
+
</lb-table>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The `head` template's content becomes the `<thead>`'s children. The `<tr>`
|
|
111
|
+
written outside any `<template>` is slot content and becomes a child of
|
|
112
|
+
`<tbody>`. There is no template addressed to `foot`, so the `<tfoot>` ships
|
|
113
|
+
empty, exactly as the definition wrote it.
|
|
114
|
+
|
|
115
|
+
## Template content elsewhere on the page
|
|
116
|
+
|
|
117
|
+
Expansion also descends into the content of any `<template>` on the page,
|
|
118
|
+
including one with no `lb-template` attribute — for example a row template
|
|
119
|
+
consumed by a widget at runtime rather than by expansion. A placeholder or
|
|
120
|
+
widget tag written inside such a template is still expanded.
|
|
121
|
+
|
|
122
|
+
## Output
|
|
123
|
+
|
|
124
|
+
`expand(source, defs)` returns HTML text. Substitution is applied to a parsed
|
|
125
|
+
tree through DOM APIs — `setAttribute`, text node values — never to a string,
|
|
126
|
+
so a value supplied through `exp-*` cannot be reparsed as markup.
|
|
127
|
+
|
|
128
|
+
What `expand` returns is what ships. No value from a request or a database is
|
|
129
|
+
added after expansion; those values land on the already-expanded tree later,
|
|
130
|
+
as attributes.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
We begin with the smallest working application. This app does not even
|
|
4
|
+
have any pages, but it will load without error, and is inspectable.
|
|
5
|
+
|
|
6
|
+
## The chrome file
|
|
7
|
+
|
|
8
|
+
Write a file ending `.chrome.html` — say, `src/chrome.chrome.html`. There
|
|
9
|
+
must be exactly one of these anywhere under `src/`; the builder finds it by
|
|
10
|
+
that name, not by which directory it's in.
|
|
11
|
+
|
|
12
|
+
At minimum, the chrome must contain:
|
|
13
|
+
|
|
14
|
+
- a link to the `client.js` script
|
|
15
|
+
- The `<lb-hub>` element just inside `<body>`
|
|
16
|
+
- an empty `<main>` inside `<lb-hub>`
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!doctype html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8" />
|
|
23
|
+
<title>Getting Started</title>
|
|
24
|
+
<script src="/client.js" defer></script>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<lb-hub>
|
|
28
|
+
<main></main>
|
|
29
|
+
</lb-hub>
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The client script is generated by the builder, and contains some core code
|
|
35
|
+
and the Javascript
|
|
36
|
+
for all custom elements in the app. Since we have only `lb-hub`, the build
|
|
37
|
+
will produce a `client.js` containing the core code and only the class for `lb-hub`.
|
|
38
|
+
|
|
39
|
+
## Serving It
|
|
40
|
+
|
|
41
|
+
Create `server.js` as an express server:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import express from "express";
|
|
45
|
+
import { readFileSync } from "node:fs";
|
|
46
|
+
|
|
47
|
+
const app = express();
|
|
48
|
+
|
|
49
|
+
app.get("/", (_req, res) =>
|
|
50
|
+
res.type("html").send(readFileSync("dist/app.html", "utf-8")),
|
|
51
|
+
);
|
|
52
|
+
app.use("/client.js", express.static("dist/client.js"));
|
|
53
|
+
|
|
54
|
+
app.listen(8787);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`dist/app.html` is read on every request rather than once at startup, so a
|
|
58
|
+
rebuild in watch mode is visible without restarting the server.
|
|
59
|
+
|
|
60
|
+
## The dev Script
|
|
61
|
+
|
|
62
|
+
Add to package.json:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"scripts": {
|
|
67
|
+
"dev": "loadbare-app-build --watch & node server.js"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`loadbare-app-build` scans `src/` by default; `--src` overrides which tree. It
|
|
73
|
+
takes no other location flags — everything under that tree is found by what
|
|
74
|
+
a file is named, not by which directory holds it:
|
|
75
|
+
|
|
76
|
+
- `*.chrome.html` — the one chrome file
|
|
77
|
+
- `*.page.html` — a page, named for the part before the suffix
|
|
78
|
+
- any other `.html` — a widget definition, named for its own filename
|
|
79
|
+
- a `.ts` file shaped like a custom element tag (e.g. `lb-nav.ts`,
|
|
80
|
+
`app-box.ts`) — a widget's script
|
|
81
|
+
- `*.hooks.ts` / `*.queries.ts` — a page's server half, paired with a
|
|
82
|
+
`*.page.html` by sharing its basename
|
|
83
|
+
- `.css` — a stylesheet, concatenated with every other one found
|
|
84
|
+
- `elements.ts` — third-party custom elements: a default export mapping tag
|
|
85
|
+
to import specifier, e.g. `{ "acme-date-picker": "@acme/date-picker" }`
|
|
86
|
+
|
|
87
|
+
A widget's markup and script don't need to sit in the same place, or in any
|
|
88
|
+
particular directory at all — `src/pages/home.page.html` and
|
|
89
|
+
`src/lb-nav.html` are both found the same way `src/widgets/lb-nav.html`
|
|
90
|
+
would be.
|
|
91
|
+
|
|
92
|
+
## CSS
|
|
93
|
+
|
|
94
|
+
Every `.css` file anywhere under `src/` is concatenated into `dist/app.css`,
|
|
95
|
+
in one order: sorted by filename, not by directory. Put a stylesheet next to
|
|
96
|
+
the markup it styles, wherever that is convenient — a global one sorts to
|
|
97
|
+
the front on its own by naming itself `00-reset.css` or `aa-globals.css`,
|
|
98
|
+
regardless of which directory it lives in. Nothing here knows about widgets
|
|
99
|
+
or pages; it is exactly the concatenation of what was authored, with no
|
|
100
|
+
scoping and no plain `style="..."` attribute anywhere the framework writes
|
|
101
|
+
markup, since that is inline code the same way `<script>` is, and Loadbare App
|
|
102
|
+
targets a CSP that refuses both.
|
|
103
|
+
|
|
104
|
+
The chrome links it like any other asset — `<link rel="stylesheet"
|
|
105
|
+
href="/app.css" />` — the builder never writes into the chrome itself.
|
|
106
|
+
|
|
107
|
+
This script runs the Loadbare App builder in watch mode, alongside the server.
|
|
108
|
+
With `--watch`, every `.html`, `.ts` and `.css` file under `src/` is watched —
|
|
109
|
+
a page, a widget's markup, a widget's class, a stylesheet, and `elements.ts`
|
|
110
|
+
all feed the build.
|
|
111
|
+
|
|
112
|
+
Loadbare App does not reload the browser on a rebuild. Watch mode keeps
|
|
113
|
+
`dist/app.html`, `dist/client.js` and `dist/app.css` current on disk; the
|
|
114
|
+
browser picks up a change on the next manual refresh.
|
|
115
|
+
|
|
116
|
+
## Run it
|
|
117
|
+
|
|
118
|
+
Try `npm run dev`, and navigate the browser to localhost:8787, you
|
|
119
|
+
should see an empty page, with a console message, and the view source
|
|
120
|
+
option should show exactly our page.
|