@loadbare/app 0.4.0 → 0.5.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/README.md +53 -82
- package/dist/build/assemble.d.ts +7 -5
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +29 -9
- package/dist/build/cli.d.ts +20 -12
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +34 -16
- package/dist/build/elements.d.ts +15 -29
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +25 -111
- package/dist/build/expand.d.ts +1 -1
- package/dist/build/expand.js +1 -1
- package/dist/build/locations.d.ts +14 -37
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +24 -67
- package/dist/build/origins.d.ts +109 -0
- package/dist/build/origins.d.ts.map +1 -0
- package/dist/build/origins.js +270 -0
- package/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +15 -8
- package/dist/core/lb-types.d.ts +2 -2
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +1 -1
- package/dist/hub/lb-hub.d.ts.map +1 -1
- package/dist/hub/lb-hub.js +44 -17
- package/dist/hub/lb-rows.js +3 -3
- package/dist/server/lb-server.d.ts +5 -4
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/tests/assemble.test.js +11 -4
- package/dist/tests/elements.test.js +47 -51
- package/dist/tests/expand.test.d.ts +1 -1
- package/dist/tests/expand.test.js +2 -2
- package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
- package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
- package/dist/tests/lb-express.test.js +1 -1
- package/dist/tests/origins.test.d.ts +10 -0
- package/dist/tests/origins.test.d.ts.map +1 -0
- package/dist/tests/origins.test.js +326 -0
- package/dist/tests/pages.test.js +3 -3
- package/dist/tests/styles.test.js +7 -4
- package/docs/reference/builder.md +128 -0
- package/docs/reference/chrome.md +75 -0
- package/docs/reference/css.md +44 -0
- package/docs/reference/custom-elements.md +327 -0
- package/docs/reference/data-binding.md +240 -0
- package/docs/reference/overview.md +38 -0
- package/docs/reference/page-files.md +175 -0
- package/docs/reference/server.md +123 -0
- package/docs/reference/widgets.md +163 -0
- package/docs/roadmap.md +130 -0
- package/docs/testing.md +228 -0
- package/docs/theory.md +344 -223
- package/docs/tutorials/000-getting-started.md +86 -0
- package/docs/tutorials/010-pages-and-navigation.md +129 -0
- package/docs/tutorials/020-css.md +103 -0
- package/docs/tutorials/030-html-decomposition.md +79 -0
- package/docs/tutorials/040-displaying-data.md +169 -0
- package/docs/tutorials/050-actions.md +77 -0
- package/docs/tutorials/060-custom-element-code.md +73 -0
- package/docs/tutorials/065-conditional-rendering.md +161 -0
- package/docs/tutorials/070-displaying-a-list.md +137 -0
- package/docs/tutorials/072-inserting-into-a-list.md +88 -0
- package/docs/tutorials/074-deleting-from-a-list.md +77 -0
- package/docs/tutorials/076-updating-a-list-item.md +86 -0
- package/docs/tutorials/080-widget-requests.md +124 -0
- package/docs/tutorials/090-using-widget-libraries.md +75 -0
- package/package.json +4 -12
- package/dist/client.js +0 -522
- package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
- package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
- package/dist/demo-static/src/widgets/app-box.js +0 -19
- package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
- package/dist/tests/golden.test.d.ts +0 -19
- package/dist/tests/golden.test.d.ts.map +0 -1
- package/dist/tests/golden.test.js +0 -60
- package/dist/tests/helpers/window.d.ts +0 -43
- package/dist/tests/helpers/window.d.ts.map +0 -1
- package/dist/tests/helpers/window.js +0 -78
- package/dist/tests/lb-input.test.d.ts +0 -9
- package/dist/tests/lb-input.test.d.ts.map +0 -1
- package/dist/tests/lb-input.test.js +0 -78
- package/dist/tests/lb-list.test.d.ts +0 -12
- package/dist/tests/lb-list.test.d.ts.map +0 -1
- package/dist/tests/lb-list.test.js +0 -44
- package/dist/tests/lb-options.test.d.ts +0 -10
- package/dist/tests/lb-options.test.d.ts.map +0 -1
- package/dist/tests/lb-options.test.js +0 -121
- package/dist/tests/lb-picker.test.d.ts +0 -14
- package/dist/tests/lb-picker.test.d.ts.map +0 -1
- package/dist/tests/lb-picker.test.js +0 -59
- package/dist/tests/lb-select.test.d.ts +0 -9
- package/dist/tests/lb-select.test.d.ts.map +0 -1
- package/dist/tests/lb-select.test.js +0 -71
- package/dist/tests/lb-table.test.d.ts +0 -15
- package/dist/tests/lb-table.test.d.ts.map +0 -1
- package/dist/tests/lb-table.test.js +0 -205
- package/dist/widgets/index.d.ts +0 -7
- package/dist/widgets/index.d.ts.map +0 -1
- package/dist/widgets/index.js +0 -6
- package/dist/widgets/lb-input.d.ts +0 -2
- package/dist/widgets/lb-input.d.ts.map +0 -1
- package/dist/widgets/lb-input.js +0 -48
- package/dist/widgets/lb-list.d.ts +0 -2
- package/dist/widgets/lb-list.d.ts.map +0 -1
- package/dist/widgets/lb-list.js +0 -17
- package/dist/widgets/lb-options.d.ts +0 -26
- package/dist/widgets/lb-options.d.ts.map +0 -1
- package/dist/widgets/lb-options.js +0 -72
- package/dist/widgets/lb-picker.d.ts +0 -2
- package/dist/widgets/lb-picker.d.ts.map +0 -1
- package/dist/widgets/lb-picker.js +0 -25
- package/dist/widgets/lb-select.d.ts +0 -2
- package/dist/widgets/lb-select.d.ts.map +0 -1
- package/dist/widgets/lb-select.js +0 -43
- package/dist/widgets/lb-table.d.ts +0 -2
- package/dist/widgets/lb-table.d.ts.map +0 -1
- package/dist/widgets/lb-table.js +0 -113
- package/docs/application-chrome.md +0 -36
- package/docs/building-html-pages.md +0 -130
- package/docs/getting-started.md +0 -120
- package/docs/guide.md +0 -1164
- package/docs/hosting.md +0 -218
- package/docs/latent-risks.md +0 -20
- package/widgets/index.ts +0 -6
- package/widgets/lb-input.html +0 -1
- package/widgets/lb-input.ts +0 -64
- package/widgets/lb-list.html +0 -1
- package/widgets/lb-list.ts +0 -21
- package/widgets/lb-options.html +0 -4
- package/widgets/lb-options.ts +0 -88
- package/widgets/lb-picker.html +0 -7
- package/widgets/lb-picker.ts +0 -27
- package/widgets/lb-select.html +0 -4
- package/widgets/lb-select.ts +0 -55
- package/widgets/lb-table.html +0 -8
- package/widgets/lb-table.ts +0 -126
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a build's files come from — see docs/reference/builder.md.
|
|
3
|
+
*
|
|
4
|
+
* Three kinds of file reach the assembled application: a `.html` definition
|
|
5
|
+
* that expands a tag, a `.ts` script that registers its class, and a `.css`
|
|
6
|
+
* stylesheet. All three arrive from the same places, so all three are found
|
|
7
|
+
* the same way: one ordered list of origins, scanned once each, read by
|
|
8
|
+
* whoever needs them.
|
|
9
|
+
*
|
|
10
|
+
* The order is the cascade, and it is the same for every file kind:
|
|
11
|
+
*
|
|
12
|
+
* 1. this package's own `hub/`
|
|
13
|
+
* 2. each package declared in `imports.ts`, in first-mention order
|
|
14
|
+
* 3. the application's own `--src` tree
|
|
15
|
+
*
|
|
16
|
+
* A package contributes one directory of its own choosing — `loadbare.widgets`
|
|
17
|
+
* in its package.json, or the whole package when it says nothing. See
|
|
18
|
+
* `widgetDir` below for why a package gets to answer that.
|
|
19
|
+
*
|
|
20
|
+
* The one built-in origin holds one tag, `lb-hub`, and it is there because
|
|
21
|
+
* every application has a hub whether or not it says so. A widget is not in
|
|
22
|
+
* that position: `@loadbare/widgets` is a declared package like any other,
|
|
23
|
+
* and the builder cannot tell it from someone else's.
|
|
24
|
+
*
|
|
25
|
+
* Two rules fall out of that list, and there is no third:
|
|
26
|
+
*
|
|
27
|
+
* - Within one origin, two files claiming the same tag are an error. That
|
|
28
|
+
* is a typo, and the origin's own author is the one who can fix it.
|
|
29
|
+
* - Across origins, the later one wins. An application always has the
|
|
30
|
+
* final say over a package it imported, and a package over a built-in,
|
|
31
|
+
* without anyone having to out-name a dependency they do not control.
|
|
32
|
+
*/
|
|
33
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
import { pathToFileURL } from "node:url";
|
|
36
|
+
/** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */
|
|
37
|
+
export const CUSTOM_ELEMENT_TAG = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
|
|
38
|
+
const HTML_SUFFIX = ".html";
|
|
39
|
+
const SCRIPT_SUFFIXES = [".ts", ".js"];
|
|
40
|
+
const CSS_SUFFIX = ".css";
|
|
41
|
+
/**
|
|
42
|
+
* Every file under `dir`, its own nested `node_modules` excluded — those
|
|
43
|
+
* belong to the packages that declare them, not to this one.
|
|
44
|
+
*/
|
|
45
|
+
async function filesUnder(dir) {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return entries
|
|
54
|
+
.filter((entry) => {
|
|
55
|
+
if (!entry.isFile())
|
|
56
|
+
return false;
|
|
57
|
+
const relative = path.relative(dir, entry.parentPath);
|
|
58
|
+
return !relative.split(path.sep).includes("node_modules");
|
|
59
|
+
})
|
|
60
|
+
.map((entry) => path.join(entry.parentPath, entry.name));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One origin's contribution, classified by filename alone.
|
|
64
|
+
*
|
|
65
|
+
* A `.html` or `.ts` file is a widget's only when its basename is a tag —
|
|
66
|
+
* which is what excludes `chrome.html`, `about.page.html`, `about.hooks.ts`
|
|
67
|
+
* and `imports.ts` here without naming any of them. Those are the
|
|
68
|
+
* application's own files, and build/locations.ts picks them out of `src`
|
|
69
|
+
* separately.
|
|
70
|
+
*/
|
|
71
|
+
export async function scanOrigin(origin) {
|
|
72
|
+
const definitions = new Map();
|
|
73
|
+
const scripts = new Map();
|
|
74
|
+
const css = [];
|
|
75
|
+
const claim = (into, what, tag, file) => {
|
|
76
|
+
const already = into.get(tag);
|
|
77
|
+
if (already) {
|
|
78
|
+
throw new Error(`origins: '${origin.name}' has two ${what} files for <${tag}>: ` +
|
|
79
|
+
`'${already}' and '${file}'`);
|
|
80
|
+
}
|
|
81
|
+
into.set(tag, file);
|
|
82
|
+
};
|
|
83
|
+
for (const dir of origin.dirs) {
|
|
84
|
+
for (const file of await filesUnder(dir)) {
|
|
85
|
+
const name = path.basename(file);
|
|
86
|
+
if (name.endsWith(CSS_SUFFIX)) {
|
|
87
|
+
css.push(file);
|
|
88
|
+
}
|
|
89
|
+
else if (name.endsWith(HTML_SUFFIX)) {
|
|
90
|
+
const tag = name.slice(0, -HTML_SUFFIX.length).toLowerCase();
|
|
91
|
+
if (CUSTOM_ELEMENT_TAG.test(tag)) {
|
|
92
|
+
claim(definitions, "definition", tag, file);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const suffix = SCRIPT_SUFFIXES.find((s) => name.endsWith(s));
|
|
97
|
+
if (!suffix)
|
|
98
|
+
continue;
|
|
99
|
+
const tag = name.slice(0, -suffix.length).toLowerCase();
|
|
100
|
+
// Without the extension: this becomes an import specifier, and
|
|
101
|
+
// esbuild resolves ".ts" from the bare path the same way as ".js".
|
|
102
|
+
// A `.d.ts` never reaches here — stripping ".ts" leaves a dot, which
|
|
103
|
+
// is not a tag.
|
|
104
|
+
if (CUSTOM_ELEMENT_TAG.test(tag)) {
|
|
105
|
+
claim(scripts, "script", tag, file.slice(0, -suffix.length));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// By filename first, the full path only to break a tie. A directory a file
|
|
111
|
+
// happens to sit in never affects the order.
|
|
112
|
+
css.sort((a, b) => path.basename(a).localeCompare(path.basename(b)) || a.localeCompare(b));
|
|
113
|
+
return {
|
|
114
|
+
name: origin.name,
|
|
115
|
+
...(origin.specifier ? { specifier: origin.specifier } : {}),
|
|
116
|
+
definitions,
|
|
117
|
+
scripts,
|
|
118
|
+
css,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* `imports.ts`, executed rather than parsed — it runs through the same
|
|
123
|
+
* loader as the rest of the build, so it is ordinary TypeScript, not a data
|
|
124
|
+
* format with its own rules. It lists the packages an application pulls
|
|
125
|
+
* widgets from, and each one becomes an origin, scanned exactly as `src` is.
|
|
126
|
+
*/
|
|
127
|
+
export async function loadElementManifest(file) {
|
|
128
|
+
let mod;
|
|
129
|
+
try {
|
|
130
|
+
// pathToFileURL requires an absolute path; the caller's may not be one.
|
|
131
|
+
mod = (await import(pathToFileURL(path.resolve(file)).href));
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err.code === "ERR_MODULE_NOT_FOUND")
|
|
135
|
+
return [];
|
|
136
|
+
throw new Error(`elements: '${file}' failed to load: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
const declared = mod.default ?? [];
|
|
139
|
+
if (!Array.isArray(declared)) {
|
|
140
|
+
throw new Error(`imports.ts: must default-export an array of package names, got ${typeof declared}`);
|
|
141
|
+
}
|
|
142
|
+
for (const name of declared) {
|
|
143
|
+
if (typeof name !== "string" || name === "") {
|
|
144
|
+
throw new Error(`imports.ts: every entry must be a package name, got ${JSON.stringify(name)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return declared;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The ordinary node_modules walk, stopping at the first directory that has
|
|
151
|
+
* the package installed — not a full resolution of an entry file, on
|
|
152
|
+
* purpose: this only needs the package's root directory, and stopping short
|
|
153
|
+
* avoids caring whether the package exposes an "exports" map at all. Which
|
|
154
|
+
* part of that root holds the widgets is the package's own answer to give —
|
|
155
|
+
* `widgetDir` asks it.
|
|
156
|
+
*/
|
|
157
|
+
export async function findInstalledPackage(packageName, fromDir) {
|
|
158
|
+
let dir = path.resolve(fromDir);
|
|
159
|
+
for (;;) {
|
|
160
|
+
const candidate = path.join(dir, "node_modules", packageName);
|
|
161
|
+
try {
|
|
162
|
+
await stat(path.join(candidate, "package.json"));
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// not here — keep walking up
|
|
167
|
+
}
|
|
168
|
+
const parent = path.dirname(dir);
|
|
169
|
+
if (parent === dir) {
|
|
170
|
+
throw new Error(`origins: could not find installed package '${packageName}' above '${fromDir}'`);
|
|
171
|
+
}
|
|
172
|
+
dir = parent;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Where in an installed package its widgets are, from the package's own
|
|
177
|
+
* manifest:
|
|
178
|
+
*
|
|
179
|
+
* "loadbare": { "widgets": "./dist" }
|
|
180
|
+
*
|
|
181
|
+
* A package that says nothing has its whole directory scanned, which is what
|
|
182
|
+
* every package got before this field existed.
|
|
183
|
+
*
|
|
184
|
+
* The field is here because a published package is not a source tree. It is a
|
|
185
|
+
* distribution artifact with a shape npm defines — compiled output in `dist/`,
|
|
186
|
+
* the source excluded, plus a README, a licence, docs and whatever else its
|
|
187
|
+
* author ships — and scanning all of it makes every one of those files a
|
|
188
|
+
* possible widget. A package that compiles then carries the same tag twice,
|
|
189
|
+
* once as `.ts` and once as `.js`, and the duplicate-tag error fires on a
|
|
190
|
+
* package the application does not control and cannot fix.
|
|
191
|
+
*
|
|
192
|
+
* So the package says where to look, and the rest of it is none of the
|
|
193
|
+
* builder's business. This costs an application author nothing: they still
|
|
194
|
+
* list the package in `imports.ts` and write no import statement. It costs the
|
|
195
|
+
* package author one line, in the file npm already expects to carry exactly
|
|
196
|
+
* this kind of declaration.
|
|
197
|
+
*/
|
|
198
|
+
async function widgetDir(root, packageName) {
|
|
199
|
+
let manifest;
|
|
200
|
+
try {
|
|
201
|
+
manifest = JSON.parse(await readFile(path.join(root, "package.json"), "utf-8"));
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
throw new Error(`origins: could not read the package.json of '${packageName}': ${err.message}`);
|
|
205
|
+
}
|
|
206
|
+
const declared = manifest.loadbare?.widgets;
|
|
207
|
+
if (declared === undefined)
|
|
208
|
+
return root;
|
|
209
|
+
if (typeof declared !== "string" || declared === "") {
|
|
210
|
+
throw new Error(`origins: '${packageName}' has a "loadbare.widgets" that is not a path: ` +
|
|
211
|
+
`${JSON.stringify(declared)}`);
|
|
212
|
+
}
|
|
213
|
+
// A path out of the package would put a package's declaration in charge of
|
|
214
|
+
// directories it does not own — including the application's own.
|
|
215
|
+
const dir = path.resolve(root, declared);
|
|
216
|
+
const inside = path.relative(root, dir);
|
|
217
|
+
if (path.isAbsolute(declared) || inside.startsWith("..")) {
|
|
218
|
+
throw new Error(`origins: '${packageName}' declares widgets at '${declared}', which is outside the package`);
|
|
219
|
+
}
|
|
220
|
+
// A declared directory that is not there means the package shipped without
|
|
221
|
+
// its widgets — its build did not run, or `files` left the directory out.
|
|
222
|
+
// Scanning the package root instead would find some of them and look like it
|
|
223
|
+
// worked, so this stops.
|
|
224
|
+
try {
|
|
225
|
+
await stat(dir);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
throw new Error(`origins: '${packageName}' declares widgets at '${declared}', which is not in the installed package`);
|
|
229
|
+
}
|
|
230
|
+
return dir;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The cascade, in order. A declared package is an origin like any other: the
|
|
234
|
+
* directory it points `loadbare.widgets` at is scanned the way `src` is — its
|
|
235
|
+
* whole installed directory, when it points at nothing — and a widget there is
|
|
236
|
+
* found by its filename, not by being named in a manifest.
|
|
237
|
+
*
|
|
238
|
+
* `hub/` is the only origin naming a specifier, because `lb-hub` is the
|
|
239
|
+
* package's main entry point rather than a file to reach into.
|
|
240
|
+
*/
|
|
241
|
+
export async function buildOrigins(builtin, declared, src) {
|
|
242
|
+
const { builtinHubDir, packageName } = builtin;
|
|
243
|
+
const packages = [];
|
|
244
|
+
for (const name of declared) {
|
|
245
|
+
const root = await findInstalledPackage(name, src);
|
|
246
|
+
packages.push({ name, dirs: [await widgetDir(root, name)] });
|
|
247
|
+
}
|
|
248
|
+
return [
|
|
249
|
+
{
|
|
250
|
+
name: packageName,
|
|
251
|
+
dirs: [builtinHubDir],
|
|
252
|
+
specifier: () => packageName,
|
|
253
|
+
},
|
|
254
|
+
...packages,
|
|
255
|
+
{ name: src, dirs: [src] },
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The whole cascade, scanned. Everything a build reads from disk that is not
|
|
260
|
+
* a page, the chrome, or a page's server half comes back from here.
|
|
261
|
+
*/
|
|
262
|
+
export async function scanAll(builtin, src, importsFile) {
|
|
263
|
+
const declared = importsFile ? await loadElementManifest(importsFile) : [];
|
|
264
|
+
const origins = await buildOrigins(builtin, declared, src);
|
|
265
|
+
return Promise.all(origins.map(scanOrigin));
|
|
266
|
+
}
|
|
267
|
+
/** Every origin's stylesheets, concatenated in cascade order. */
|
|
268
|
+
export function cssFrom(scans) {
|
|
269
|
+
return scans.flatMap((scan) => scan.css);
|
|
270
|
+
}
|
|
@@ -14,6 +14,7 @@ export declare const ATTR_SLOT = "lb-slot";
|
|
|
14
14
|
export declare const ATTR_TEMPLATE = "lb-template";
|
|
15
15
|
export declare const ATTR_NAV_LINK = "lb-nav-link";
|
|
16
16
|
export declare const PAGE_TEMPLATE_PREFIX = "page-";
|
|
17
|
+
export declare const ATTR_UNKNOWN_PAGE = "lb-unknown-page";
|
|
17
18
|
export declare const HUB_TAG_NAME = "lb-hub";
|
|
18
19
|
export declare const ATTR_PENDING = "data-lb-pending";
|
|
19
20
|
export declare const ATTR_ERROR = "data-lb-error";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,aAAa,CAAC;AACrC,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,SAAS,YAAY,CAAC;AACnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAMrC,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,SAAS,YAAY,CAAC;AAOnC,eAAO,MAAM,cAAc,cAAc,CAAC;AAK1C,eAAO,MAAM,aAAa,eAAe,CAAC;AAM1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAKvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAOvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAQ5C,eAAO,MAAM,YAAY,WAAW,CAAC;AAQrC,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAO9C,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAG1C,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAIzC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,aAAa,CAAC;AACrC,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,SAAS,YAAY,CAAC;AACnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAMrC,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,SAAS,YAAY,CAAC;AAOnC,eAAO,MAAM,cAAc,cAAc,CAAC;AAK1C,eAAO,MAAM,aAAa,eAAe,CAAC;AAM1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAKvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAOvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAQ5C,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAQnD,eAAO,MAAM,YAAY,WAAW,CAAC;AAQrC,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAO9C,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAG1C,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAIzC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Every hub attribute name lives here. Nothing writes one as a string
|
|
2
|
-
// literal — not a widget, not a page host, not a test. See
|
|
3
|
-
//
|
|
4
|
-
// ---
|
|
2
|
+
// literal — not a widget, not a page host, not a test. See
|
|
3
|
+
// docs/reference/data-binding.md.
|
|
4
|
+
// --- Binding (docs/reference/data-binding.md) ---------------------------
|
|
5
5
|
export const ATTR_QUERY = "lb-query";
|
|
6
6
|
export const ATTR_KEY = "lb-key";
|
|
7
7
|
export const ATTR_CELL = "lb-cell";
|
|
@@ -17,11 +17,11 @@ export const ATTR_GROUP = "lb-group";
|
|
|
17
17
|
export const ATTR_SORT = "lb-sort";
|
|
18
18
|
// Stamped on a list widget by the shared row machinery: how many rows it is
|
|
19
19
|
// showing. It is HTML's namespace rather than Loadbare App's because nothing
|
|
20
|
-
//
|
|
20
|
+
// binds to it — it is derived from rows the hub already knows, exactly as
|
|
21
21
|
// the section heading in lb-table is. It exists so that "no members yet"
|
|
22
22
|
// is a stylesheet rule rather than a conditional in every list widget.
|
|
23
23
|
export const ATTR_ROW_COUNT = "data-rows";
|
|
24
|
-
// --- Requests (docs/
|
|
24
|
+
// --- Requests (docs/reference/data-binding.md) --------------------------
|
|
25
25
|
// One event name, used by a widget reaching the server and by a widget
|
|
26
26
|
// reaching an ancestor widget.
|
|
27
27
|
export const LB_EVENT_NAME = "lb-request";
|
|
@@ -32,7 +32,7 @@ export const LB_EVENT_NAME = "lb-request";
|
|
|
32
32
|
export const ATTR_ACTION = "lb-action";
|
|
33
33
|
// Marks a native element as deleting the tuple it is inside — the CRUD
|
|
34
34
|
// counterpart to lb-action. It needs no declared name: tuple-delete takes
|
|
35
|
-
// only the row's own lb-query/lb-key
|
|
35
|
+
// only the row's own lb-query/lb-key binding, which is all the server
|
|
36
36
|
// needs to know which row and whether the operation is permitted on it.
|
|
37
37
|
export const ATTR_DELETE = "lb-delete";
|
|
38
38
|
// Marks a <form> as building a new tuple on submit. The hub gathers every
|
|
@@ -45,7 +45,7 @@ export const ATTR_INSERT = "lb-insert";
|
|
|
45
45
|
// a list's row template root, or sits inside one, has both from its
|
|
46
46
|
// ancestors exactly as a delete button does.
|
|
47
47
|
export const ATTR_UPDATE = "lb-update";
|
|
48
|
-
// --- Expansion (docs/
|
|
48
|
+
// --- Expansion (docs/reference/custom-elements.md) ---------------------
|
|
49
49
|
// Marks the element in a definition whose children receive the authored
|
|
50
50
|
// inner content. An attribute rather than an element because HTML content
|
|
51
51
|
// models discard foreign elements inside <select> and <table>.
|
|
@@ -61,12 +61,19 @@ export const ATTR_SLOT = "lb-slot";
|
|
|
61
61
|
// is what gets it to expansion intact — and unwrapping it is what puts real
|
|
62
62
|
// elements in the shipped document.
|
|
63
63
|
export const ATTR_TEMPLATE = "lb-template";
|
|
64
|
-
// --- Host channel (docs/
|
|
64
|
+
// --- Host channel (docs/reference/chrome.md) ---------------------------
|
|
65
65
|
// Only anchors carrying this are intercepted, so external links are never
|
|
66
66
|
// hijacked.
|
|
67
67
|
export const ATTR_NAV_LINK = "lb-nav-link";
|
|
68
68
|
// A page host ships as <template id="page-<name>">.
|
|
69
69
|
export const PAGE_TEMPLATE_PREFIX = "page-";
|
|
70
|
+
// Marks the chrome's own <dialog> for a page name that resolves to no
|
|
71
|
+
// template — a routing miss discovered client-side, not an HTTP 404 (every
|
|
72
|
+
// route gets the same 200 response; see docs/tutorials/010-pages-and-navigation.md).
|
|
73
|
+
// Optional: an application that declares none gets today's console.error
|
|
74
|
+
// and nothing more. The builder requires the element it's on to be a
|
|
75
|
+
// <dialog> (build/assemble.ts), since the hub calls showModal() on it.
|
|
76
|
+
export const ATTR_UNKNOWN_PAGE = "lb-unknown-page";
|
|
70
77
|
// The hub's own tag. It is exempt from the rule every other LB-* tag
|
|
71
78
|
// follows: expansion does not require it to have a definition (build/expand.ts),
|
|
72
79
|
// and the client build does not require it to have a script of its own
|
package/dist/core/lb-types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** docs/
|
|
1
|
+
/** docs/reference/data-binding.md — the operation set is closed. */
|
|
2
2
|
export type HubRequest = {
|
|
3
3
|
op: "cell-change";
|
|
4
4
|
query: string;
|
|
@@ -43,7 +43,7 @@ export type HubRequest = {
|
|
|
43
43
|
/** One query's result: the cells of a single tuple. Every value is a string. */
|
|
44
44
|
export type QueryResult = Record<string, string>;
|
|
45
45
|
/**
|
|
46
|
-
* A query that returns many tuples — see docs/
|
|
46
|
+
* A query that returns many tuples — see docs/reference/server.md, "Queries".
|
|
47
47
|
*
|
|
48
48
|
* Two results, because a list widget must be told the difference. `rows` is
|
|
49
49
|
* the entire set and therefore also the order; `patch` names only what
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAClB;IACE,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACrE;IACE,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACpD;;;;;;;;;;;;GAYG;GACD;IACE,EAAE,EAAE,QAAQ,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3D,sEAAsE;AACtE,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEjD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,IAAI,UAAU,CAGpE;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACtC"}
|
package/dist/hub/lb-apply.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Landing data on a live host — see docs/
|
|
3
|
+
* Landing data on a live host — see docs/reference/data-binding.md.
|
|
4
4
|
*
|
|
5
5
|
* This is one file rather than part of the hub because a list widget needs
|
|
6
6
|
* the same operation. It clones a row and fills it with a tuple, and filling
|
package/dist/hub/lb-hub.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-hub.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-hub.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/hub/lb-hub.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import { ATTR_ACTION, ATTR_CELL, ATTR_DELETE, ATTR_ERROR, ATTR_INSERT, ATTR_KEY, ATTR_NAV_LINK, ATTR_PENDING, ATTR_QUERY, ATTR_UPDATE, HUB_TAG_NAME, PAGE_TEMPLATE_PREFIX, LB_DATA_ENDPOINT, LB_EVENT_NAME, LB_REQUEST_ENDPOINT, LB_REQUEST_TIMEOUT_MS, } from "../core/lb-constants";
|
|
3
|
-
import { applyData } from "./lb-apply";
|
|
2
|
+
import { ATTR_ACTION, ATTR_CELL, ATTR_DELETE, ATTR_ERROR, ATTR_INSERT, ATTR_KEY, ATTR_NAV_LINK, ATTR_PENDING, ATTR_QUERY, ATTR_UNKNOWN_PAGE, ATTR_UPDATE, HUB_TAG_NAME, PAGE_TEMPLATE_PREFIX, LB_DATA_ENDPOINT, LB_EVENT_NAME, LB_REQUEST_ENDPOINT, LB_REQUEST_TIMEOUT_MS, } from "../core/lb-constants";
|
|
3
|
+
import { applyData, applyTuple } from "./lb-apply";
|
|
4
4
|
export { applyData, applyTuple } from "./lb-apply";
|
|
5
5
|
/**
|
|
6
|
-
* Where a click happened, in the
|
|
6
|
+
* Where a click happened, in the binding vocabulary. An action carries
|
|
7
7
|
* these and nothing else, so there is no channel for a browser-supplied
|
|
8
8
|
* argument.
|
|
9
9
|
*/
|
|
10
|
-
function
|
|
10
|
+
function binding(el) {
|
|
11
11
|
const query = el.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
|
|
12
12
|
const key = el.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
|
|
13
13
|
const cell = el.getAttribute(ATTR_CELL);
|
|
@@ -51,10 +51,15 @@ function gatherValues(form) {
|
|
|
51
51
|
}
|
|
52
52
|
return values;
|
|
53
53
|
}
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* A path names a page host, and nothing finer. The bare path resolves to
|
|
56
|
+
* `index`, the same convention a static file server follows — an
|
|
57
|
+
* application names its landing page that way rather than the hub singling
|
|
58
|
+
* one out.
|
|
59
|
+
*/
|
|
55
60
|
function pageNameFor(path) {
|
|
56
61
|
const name = path.replace(/^\/+|\/+$/g, "");
|
|
57
|
-
return name === "" ? "
|
|
62
|
+
return name === "" ? "index" : name;
|
|
58
63
|
}
|
|
59
64
|
/**
|
|
60
65
|
* Fetch one round trip against the client-side deadline. Throws — rather
|
|
@@ -109,7 +114,7 @@ class LbHub extends HTMLElement {
|
|
|
109
114
|
headers: { "content-type": "application/json" },
|
|
110
115
|
body: JSON.stringify(request),
|
|
111
116
|
})
|
|
112
|
-
.then((data) => applyData(this
|
|
117
|
+
.then((data) => applyData(this, data))
|
|
113
118
|
.catch((err) => {
|
|
114
119
|
console.error(`lb-hub: request failed`, err);
|
|
115
120
|
origin?.setAttribute(ATTR_ERROR, "");
|
|
@@ -136,37 +141,37 @@ class LbHub extends HTMLElement {
|
|
|
136
141
|
const detail = {
|
|
137
142
|
op: "action",
|
|
138
143
|
name: el.getAttribute(ATTR_ACTION),
|
|
139
|
-
...
|
|
144
|
+
...binding(el),
|
|
140
145
|
};
|
|
141
146
|
el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
142
147
|
});
|
|
143
148
|
// The CRUD counterpart to the lb-action delegation above: a native
|
|
144
149
|
// element needs no declared name to delete the row it is in, only its
|
|
145
|
-
// own query/key
|
|
150
|
+
// own query/key binding.
|
|
146
151
|
this.addEventListener("click", (e) => {
|
|
147
152
|
const el = e.target?.closest(`[${ATTR_DELETE}]`);
|
|
148
153
|
if (!el || el.localName.includes("-"))
|
|
149
154
|
return;
|
|
150
155
|
e.preventDefault();
|
|
151
|
-
const { query, key } =
|
|
156
|
+
const { query, key } = binding(el);
|
|
152
157
|
if (!query || !key) {
|
|
153
|
-
console.warn(`lb-hub: lb-delete with no query/key
|
|
158
|
+
console.warn(`lb-hub: lb-delete with no query/key binding, ignoring`, el);
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
161
|
const detail = { op: "tuple-delete", query, key };
|
|
157
162
|
el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
158
163
|
});
|
|
159
|
-
// The CRUD op that gathers rather than
|
|
164
|
+
// The CRUD op that gathers rather than binds a single cell: every
|
|
160
165
|
// lb-cell inside the form becomes one entry of the values map. There is
|
|
161
166
|
// no key, because there is no row yet — only the query, found the same
|
|
162
|
-
// way
|
|
167
|
+
// way binding() finds one for a click, says what it is inserting
|
|
163
168
|
// into.
|
|
164
169
|
this.addEventListener("submit", (e) => {
|
|
165
170
|
const form = e.target?.closest(`[${ATTR_INSERT}]`);
|
|
166
171
|
if (!form)
|
|
167
172
|
return;
|
|
168
173
|
e.preventDefault();
|
|
169
|
-
const { query } =
|
|
174
|
+
const { query } = binding(form);
|
|
170
175
|
if (!query) {
|
|
171
176
|
console.warn(`lb-hub: lb-insert with no lb-query, ignoring`, form);
|
|
172
177
|
return;
|
|
@@ -187,9 +192,9 @@ class LbHub extends HTMLElement {
|
|
|
187
192
|
if (!form)
|
|
188
193
|
return;
|
|
189
194
|
e.preventDefault();
|
|
190
|
-
const { query, key } =
|
|
195
|
+
const { query, key } = binding(form);
|
|
191
196
|
if (!query || !key) {
|
|
192
|
-
console.warn(`lb-hub: lb-update with no query/key
|
|
197
|
+
console.warn(`lb-hub: lb-update with no query/key binding, ignoring`, form);
|
|
193
198
|
return;
|
|
194
199
|
}
|
|
195
200
|
const detail = {
|
|
@@ -223,6 +228,7 @@ class LbHub extends HTMLElement {
|
|
|
223
228
|
const template = document.getElementById(PAGE_TEMPLATE_PREFIX + page);
|
|
224
229
|
if (!(template instanceof HTMLTemplateElement)) {
|
|
225
230
|
console.error(`lb-hub: no page host for '${page}'`);
|
|
231
|
+
this.reportUnknownPage(page);
|
|
226
232
|
return;
|
|
227
233
|
}
|
|
228
234
|
let data = {};
|
|
@@ -236,7 +242,28 @@ class LbHub extends HTMLElement {
|
|
|
236
242
|
// Insert and hydrate in one synchronous block: the browser does not
|
|
237
243
|
// paint mid-task, so there is no empty flash.
|
|
238
244
|
this.main.replaceChildren(template.content.cloneNode(true));
|
|
239
|
-
applyData(this
|
|
245
|
+
applyData(this, data);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A path that resolves to no page host is discovered client-side, after a
|
|
249
|
+
* successful 200 — every route gets the same document, so there is no
|
|
250
|
+
* HTTP 404 to report (see docs/tutorials/010-pages-and-navigation.md).
|
|
251
|
+
* lb-unknown-page is optional: an application that declares none gets
|
|
252
|
+
* nothing beyond navigate()'s console.error. The builder guarantees the
|
|
253
|
+
* element it's on is a <dialog> (build/assemble.ts), so this can call
|
|
254
|
+
* showModal() without checking the tag here.
|
|
255
|
+
*
|
|
256
|
+
* Scoped to the hub, like every other lookup here, so that one rule holds
|
|
257
|
+
* without exception: what the hub acts on is inside the hub.
|
|
258
|
+
*/
|
|
259
|
+
reportUnknownPage(page) {
|
|
260
|
+
const dialog = this.querySelector(`[${ATTR_UNKNOWN_PAGE}]`);
|
|
261
|
+
if (!dialog)
|
|
262
|
+
return;
|
|
263
|
+
applyTuple(dialog, { page });
|
|
264
|
+
const modal = dialog;
|
|
265
|
+
if (!modal.open)
|
|
266
|
+
modal.showModal();
|
|
240
267
|
}
|
|
241
268
|
}
|
|
242
269
|
customElements.define(HUB_TAG_NAME, LbHub);
|
package/dist/hub/lb-rows.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* The row machinery a list widget shares — see docs/
|
|
4
|
-
*
|
|
3
|
+
* The row machinery a list widget shares — see docs/reference/custom-elements.md,
|
|
4
|
+
* "Accepting rows".
|
|
5
5
|
*
|
|
6
6
|
* A widget owns where a row goes and nothing else. Cloning the template,
|
|
7
7
|
* matching a tuple to the row already showing it, and filling that row are
|
|
@@ -101,6 +101,6 @@ export function applyRows(scope, result, place = before) {
|
|
|
101
101
|
// one conditional a page cannot be sent, since the server answers with rows
|
|
102
102
|
// and says nothing about how many survived reconciliation. A page says what
|
|
103
103
|
// an empty list looks like in a stylesheet, and no list widget carries code
|
|
104
|
-
// for it. See docs/
|
|
104
|
+
// for it. See docs/reference/data-binding.md.
|
|
105
105
|
scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope, template).size));
|
|
106
106
|
}
|
|
@@ -41,7 +41,7 @@ export declare function patch(change: {
|
|
|
41
41
|
/** The shape of a `<name>.queries.ts` module. */
|
|
42
42
|
export type Queries = Record<string, Query>;
|
|
43
43
|
/**
|
|
44
|
-
* Where the interaction happened, in the
|
|
44
|
+
* Where the interaction happened, in the binding vocabulary, plus the one
|
|
45
45
|
* value a control may carry.
|
|
46
46
|
*
|
|
47
47
|
* The browser fills these from attributes it already has. It never names a
|
|
@@ -117,7 +117,7 @@ export interface Crud {
|
|
|
117
117
|
*
|
|
118
118
|
* `crud` is the same rule for the typed CRUD operations, keyed by the query
|
|
119
119
|
* they operate on rather than by a declared name — there is nothing to name,
|
|
120
|
-
* since the row's own
|
|
120
|
+
* since the row's own binding says what it is.
|
|
121
121
|
*/
|
|
122
122
|
export interface Hooks {
|
|
123
123
|
beforeGet?: (ctx: HubContext) => void | Promise<void>;
|
|
@@ -130,8 +130,9 @@ export interface Page {
|
|
|
130
130
|
hooks: Hooks;
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
-
* The page registry.
|
|
134
|
-
*
|
|
133
|
+
* The page registry. The builder populates this automatically from every
|
|
134
|
+
* `.hooks.ts`/`.queries.ts` pair it discovers — see docs/reference/builder.md,
|
|
135
|
+
* "Generating the server-side page registry".
|
|
135
136
|
*/
|
|
136
137
|
export type Pages = Record<string, Page>;
|
|
137
138
|
export interface Hub {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,SAAS,EACV,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,UAAU;CAAG;AAE9B;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAExE;;;GAGG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,UAAU,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE;IAC5B,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,UAAU,CAEb;AAED,iDAAiD;AACjD,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,CACH,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,KAAK,KACT,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACpD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AACvE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAEpF;;;;;GAKG;AACH,MAAM,WAAW,IAAI;IACnB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,KAAK;IACpB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACd;AAED
|
|
1
|
+
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import { tmpdir } from "node:os";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { assemble } from "../build/assemble";
|
|
13
13
|
import { resolveLocations } from "../build/locations";
|
|
14
|
+
import { scanAll } from "../build/origins";
|
|
14
15
|
async function tree(files) {
|
|
15
16
|
const dir = await mkdtemp(path.join(tmpdir(), "loadbare-assemble-"));
|
|
16
17
|
for (const [name, content] of Object.entries(files)) {
|
|
@@ -20,15 +21,21 @@ async function tree(files) {
|
|
|
20
21
|
}
|
|
21
22
|
return dir;
|
|
22
23
|
}
|
|
24
|
+
async function scansFor(locations) {
|
|
25
|
+
return scanAll(locations, locations.srcDir, locations.importsFile);
|
|
26
|
+
}
|
|
23
27
|
describe("assemble", () => {
|
|
24
28
|
it("expands a widget written in the chrome, not only in pages", async () => {
|
|
25
29
|
const src = await tree({
|
|
26
|
-
"chrome.
|
|
30
|
+
"chrome.html": `<!doctype html><body><lb-nav exp-label="Site"><a href="/a">A</a></lb-nav><lb-hub><main></main></lb-hub></body>`,
|
|
27
31
|
"lb-nav.html": `<nav aria-label="{{label}}" lb-slot></nav>`,
|
|
28
32
|
});
|
|
29
33
|
try {
|
|
30
34
|
const locations = await resolveLocations({ src, out: `${src}/dist` });
|
|
31
|
-
const { html, usedTags } = await assemble(
|
|
35
|
+
const { html, usedTags } = await assemble({
|
|
36
|
+
...locations,
|
|
37
|
+
scans: await scansFor(locations),
|
|
38
|
+
});
|
|
32
39
|
assert.match(html, /<nav aria-label="Site"><a href="\/a">A<\/a><\/nav>/);
|
|
33
40
|
assert.ok(usedTags.has("lb-nav"));
|
|
34
41
|
}
|
|
@@ -38,11 +45,11 @@ describe("assemble", () => {
|
|
|
38
45
|
});
|
|
39
46
|
it("still throws when the chrome uses an undefined widget", async () => {
|
|
40
47
|
const src = await tree({
|
|
41
|
-
"chrome.
|
|
48
|
+
"chrome.html": `<!doctype html><body><lb-nope></lb-nope></body>`,
|
|
42
49
|
});
|
|
43
50
|
try {
|
|
44
51
|
const locations = await resolveLocations({ src, out: `${src}/dist` });
|
|
45
|
-
await assert.rejects(() => assemble(locations), /<lb-nope> has no definition/);
|
|
52
|
+
await assert.rejects(async () => assemble({ ...locations, scans: await scansFor(locations) }), /<lb-nope> has no definition/);
|
|
46
53
|
}
|
|
47
54
|
finally {
|
|
48
55
|
await rm(src, { recursive: true, force: true });
|