@loadbare/app 0.10.0 → 0.11.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/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +46 -37
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +92 -7
- package/dist/build/expand.js.map +1 -1
- package/dist/core/lb-constants.d.ts +2 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +8 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +31 -16
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +6 -7
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/TECHREF-1.0.md +50 -1
- package/docs/comparison.md +25 -4
- package/docs/reference/custom-elements.md +101 -9
- package/docs/reference/data-binding.md +9 -2
- package/docs/reference/page-files.md +29 -0
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +27 -5
- package/skills/loadbare-app/references/TECHREF-1.0.md +50 -1
- package/skills/loadbare-app/references/custom-elements.md +101 -9
- package/skills/loadbare-app/references/data-binding.md +9 -2
- package/skills/loadbare-app/references/page-files.md +29 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwBH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA6M/C,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,wBAAsB,QAAQ,CAAC,EAC7B,UAAU,EACV,KAAK,EACL,KAAK,GACN,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAsDtC"}
|
package/dist/build/assemble.js
CHANGED
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
* looking for pages or widgets; it is handed exactly which files those are.
|
|
18
18
|
*/
|
|
19
19
|
import { readFile } from "node:fs/promises";
|
|
20
|
-
import {
|
|
21
|
-
import { addDefinitions, customElementTags, expand, } from "./expand.js";
|
|
20
|
+
import { addDefinitions, customElementTags, expandTree, } from "./expand.js";
|
|
22
21
|
import { formatHtml } from "./format.js";
|
|
23
22
|
import { ATTR_PAGE, ATTR_PAGE_TITLE, ATTR_QUERY, ATTR_REQUEST, ATTR_SHOW, ATTR_URL_LINK, ATTR_URL_PUSH, ATTR_URL_UNKNOWN, DEVELOPER_ATTRIBUTES, HUB_TAG_NAME, LB_RESERVED_PREFIX, } from "../core/lb-constants.js";
|
|
24
23
|
import { isRowRequest } from "../core/lb-types.js";
|
|
@@ -28,9 +27,8 @@ import { isRowRequest } from "../core/lb-types.js";
|
|
|
28
27
|
* everything else it acts on. A dialog elsewhere in the body would never open
|
|
29
28
|
* and would say nothing about why.
|
|
30
29
|
*/
|
|
31
|
-
function checkUnknownPageDialog(chromeFile,
|
|
32
|
-
const
|
|
33
|
-
const el = fragment.querySelector(`[${ATTR_URL_UNKNOWN}]`);
|
|
30
|
+
function checkUnknownPageDialog(chromeFile, body) {
|
|
31
|
+
const el = body.querySelector(`[${ATTR_URL_UNKNOWN}]`);
|
|
34
32
|
if (!el)
|
|
35
33
|
return;
|
|
36
34
|
if (!el.closest(HUB_TAG_NAME)) {
|
|
@@ -95,7 +93,7 @@ function walkMarkup(node, scope, problems) {
|
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
walkMarkup(el, here, problems);
|
|
98
|
-
// By tag name rather than by instanceof:
|
|
96
|
+
// By tag name rather than by instanceof: template content belongs to a
|
|
99
97
|
// document with no window, so the constructor is not reachable from the
|
|
100
98
|
// node.
|
|
101
99
|
if (tag === "template") {
|
|
@@ -120,15 +118,12 @@ function walkMarkup(node, scope, problems) {
|
|
|
120
118
|
* A page is in the document before its data arrives, and a value that has not
|
|
121
119
|
* arrived is no value.
|
|
122
120
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* gone into a template, because the references are held.
|
|
121
|
+
* Elements are collected before any is moved, and a wrapped element is still
|
|
122
|
+
* reached once its outer one has gone into a template, because the references
|
|
123
|
+
* are held.
|
|
127
124
|
*/
|
|
128
|
-
function wrapShown(
|
|
129
|
-
const doc =
|
|
130
|
-
const holder = doc.createElement("template");
|
|
131
|
-
holder.innerHTML = html;
|
|
125
|
+
function wrapShown(holder) {
|
|
126
|
+
const doc = holder.ownerDocument;
|
|
132
127
|
const shown = [];
|
|
133
128
|
const collect = (root) => {
|
|
134
129
|
shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));
|
|
@@ -137,20 +132,32 @@ function wrapShown(html) {
|
|
|
137
132
|
}
|
|
138
133
|
};
|
|
139
134
|
collect(holder.content);
|
|
140
|
-
if (shown.length === 0)
|
|
141
|
-
return html;
|
|
142
135
|
for (const el of shown) {
|
|
143
136
|
const parked = doc.createElement("template");
|
|
144
137
|
parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW));
|
|
145
138
|
el.replaceWith(parked);
|
|
146
139
|
parked.content.append(el);
|
|
147
140
|
}
|
|
148
|
-
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Expand one file's markup, naming the file if expansion refuses it.
|
|
144
|
+
*
|
|
145
|
+
* What comes back is the tree the browser will build, so every pass after
|
|
146
|
+
* this reads and changes it in place, and it is serialized once, when it
|
|
147
|
+
* ships.
|
|
148
|
+
*/
|
|
149
|
+
function expandFile(file, source, defs) {
|
|
150
|
+
try {
|
|
151
|
+
return expandTree(source, defs);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
throw new Error(`assemble: '${file}': ${e.message}`);
|
|
155
|
+
}
|
|
149
156
|
}
|
|
150
157
|
/** Check one expanded fragment, and name the file if anything is wrong. */
|
|
151
|
-
function checkMarkup(file,
|
|
158
|
+
function checkMarkup(file, root) {
|
|
152
159
|
const problems = [];
|
|
153
|
-
walkMarkup(
|
|
160
|
+
walkMarkup(root, null, problems);
|
|
154
161
|
if (problems.length > 0) {
|
|
155
162
|
throw new Error(`assemble: '${file}' markup:\n ` + problems.join(`\n `));
|
|
156
163
|
}
|
|
@@ -160,16 +167,13 @@ function checkMarkup(file, html) {
|
|
|
160
167
|
* a fragment inside `<main>` and a `<title>` belongs in a document's head,
|
|
161
168
|
* and its text content is stamped on the page as ATTR_PAGE_TITLE.
|
|
162
169
|
*/
|
|
163
|
-
function liftTitle(
|
|
164
|
-
const doc = new JSDOM("").window.document;
|
|
165
|
-
const holder = doc.createElement("template");
|
|
166
|
-
holder.innerHTML = html;
|
|
170
|
+
function liftTitle(holder) {
|
|
167
171
|
const title = holder.content.querySelector("title");
|
|
168
172
|
if (!title)
|
|
169
|
-
return
|
|
173
|
+
return null;
|
|
170
174
|
const text = title.textContent?.trim() ?? "";
|
|
171
175
|
title.remove();
|
|
172
|
-
return
|
|
176
|
+
return text;
|
|
173
177
|
}
|
|
174
178
|
/** An attribute value, escaped for a double-quoted attribute. */
|
|
175
179
|
function attributeValue(text) {
|
|
@@ -183,13 +187,19 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
183
187
|
for (const scan of scans) {
|
|
184
188
|
defs = await addDefinitions(defs, scan.definitions);
|
|
185
189
|
}
|
|
190
|
+
// Tags are collected from each tree as it goes by. What ships is these
|
|
191
|
+
// trees and the chrome around its body, which is collected on its own.
|
|
192
|
+
const usedTags = new Set();
|
|
186
193
|
const templates = [];
|
|
187
194
|
for (const { name, file } of pages) {
|
|
188
195
|
const authored = await readFile(file, "utf-8");
|
|
189
|
-
const
|
|
190
|
-
|
|
196
|
+
const host = expandFile(file, authored, defs);
|
|
197
|
+
const title = liftTitle(host);
|
|
198
|
+
checkMarkup(file, host.content);
|
|
199
|
+
wrapShown(host);
|
|
200
|
+
customElementTags(host.content, usedTags);
|
|
191
201
|
const titled = title === null ? "" : ` ${ATTR_PAGE_TITLE}="${attributeValue(title)}"`;
|
|
192
|
-
templates.push(`<template ${ATTR_PAGE}="${name}"${titled}>${
|
|
202
|
+
templates.push(`<template ${ATTR_PAGE}="${name}"${titled}>${host.innerHTML}</template>`);
|
|
193
203
|
}
|
|
194
204
|
let chromeSource;
|
|
195
205
|
try {
|
|
@@ -203,14 +213,13 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
203
213
|
throw new Error(`assemble: '${chromeFile}' has no <body>...</body> to expand`);
|
|
204
214
|
}
|
|
205
215
|
const [whole, bodyAttrs, bodyContent] = body;
|
|
206
|
-
const
|
|
207
|
-
checkUnknownPageDialog(chromeFile,
|
|
208
|
-
checkMarkup(chromeFile,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
216
|
+
const chromeBody = expandFile(chromeFile, bodyContent, defs);
|
|
217
|
+
checkUnknownPageDialog(chromeFile, chromeBody.content);
|
|
218
|
+
checkMarkup(chromeFile, chromeBody.content);
|
|
219
|
+
wrapShown(chromeBody);
|
|
220
|
+
customElementTags(chromeBody.content, usedTags);
|
|
221
|
+
customElementTags(chromeSource.replace(whole, ""), usedTags);
|
|
222
|
+
const withPages = chromeSource.replace(whole, () => `<body${bodyAttrs}>${chromeBody.innerHTML}\n${templates.join("\n")}\n</body>`);
|
|
223
|
+
return { html: await formatHtml(withPages), usedTags };
|
|
215
224
|
}
|
|
216
225
|
//# sourceMappingURL=assemble.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,MAAM,GAEP,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,EACZ,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAInD;;;;;GAKG;AACH,SAAS,sBAAsB,CAC7B,UAAkB,EAClB,YAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAC3D,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,cAAc,gBAAgB,WAAW;YAC/D,IAAI,YAAY,kCAAkC,CACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAYD;;;;;;;;;;GAUG;AACH,SAAS,UAAU,CACjB,IAAgB,EAChB,KAAmB,EACnB,QAAkB;IAElB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC;QACzB,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,IAAI,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;QAE3D,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;YACrC,IACE,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC;gBACnC,CAAE,oBAA0C,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC3D,CAAC;gBACD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,IAAI,0CAA0C;oBAChE,QAAQ,CACX,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC9C,IACE,OAAO,KAAK,IAAI;YAChB,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC;YACtC,CAAC,YAAY,CAAC,OAAO,CAAC,EACtB,CAAC;YACD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,YAAY,KAAK,OAAO,uBAAuB;gBACjE,IAAI,kBAAkB,0CAA0C,CACnE,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,aAAa,aAAa,qBAAqB,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACvD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,aAAa,WAAW,YAAY,YAAY;gBAClE,sBAAsB,YAAY,EAAE,CACvC,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,gBAAgB,yBAAyB,CAC9D,CAAC;QACJ,CAAC;QAED,uEAAuE;QACvE,mEAAmE;QACnE,oEAAoE;QACpE,SAAS;QACT,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CACX,wBAAwB,SAAS,KAAK,IAAI,qBAAqB;oBAC7D,sDAAsD,CACzD,CAAC;YACJ,CAAC;iBAAM,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;gBACjD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,SAAS,KAAK,IAAI,0BAA0B;oBAC9D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;QAED,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC/B,wEAAwE;QACxE,wEAAwE;QACxE,QAAQ;QACR,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,OAAO,GAAI,EAA0B,CAAC,OAAO,CAAC;YACpD,MAAM,aAAa,GACjB,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC;YACnE,IAAI,aAAa,EAAE,CAAC;gBAClB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACpC,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;wBACjC,QAAQ,CAAC,IAAI,CACX,IAAI,IAAI,CAAC,SAAS,yCAAyC;4BACzD,GAAG,SAAS,8CAA8C;4BAC1D,WAAW,CACd,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,IAAgB,EAAQ,EAAE;QACzC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;QACvD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;YAClD,OAAO,CAAE,CAAyB,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAE,CAAC,CAAC;QAC5D,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED,2EAA2E;AAC3E,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,KAAK,CAAC,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,iEAAiE;AACjE,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAeD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,EAC7B,UAAU,EACV,KAAK,EACL,KAAK,GACW;IAChB,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,IAAI,IAAI,GAAgB,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QAChE,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxB,MAAM,MAAM,GACV,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,KAAK,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC;QACzE,SAAS,CAAC,IAAI,CACZ,aAAa,SAAS,KAAK,IAAI,IAAI,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC,aAAa,CAC1E,CAAC;IACJ,CAAC;IAED,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,GAAG,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,IAAI,GAAG,iCAAiC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAClE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,qCAAqC,CAC9D,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;IAC7C,MAAM,YAAY,GAAG,MAAM,CAAC,WAAY,EAAE,IAAI,CAAC,CAAC;IAChD,sBAAsB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CACpC,KAAM,EACN,GAAG,EAAE,CAAC,QAAQ,SAAS,IAAI,WAAW,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAC3E,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,MAAM,UAAU,CAAC,SAAS,CAAC;QACjC,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC;KACvC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Assembling one document out of a chrome file and a set of pages.\n *\n * Every `.html` file in play — the chrome and each page — goes through the\n * same `expand()` (see build/expand.ts) against the same definitions: the\n * built-in widgets and whatever definitions were found in the caller's own\n * source tree. `expand()` only ever sees a fragment, so the chrome's own is\n * the text between its `<body>` tags; a page's is the whole file. A page's\n * expanded host is wrapped in `<template lb-page=\"<name>\">` and, together\n * with the chrome's own expanded body content, spliced back into the\n * chrome's `<body>`. The result is the one document a server has to serve\n * for every route — see docs/tutorials/000-getting-started.md and\n * docs/reference/builder.md.\n *\n * Every path here is a parameter, resolved once by build/locations.ts — this\n * module never reconstructs one of its own, and never reads a directory\n * looking for pages or widgets; it is handed exactly which files those are.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport {\n addDefinitions,\n customElementTags,\n expand,\n type Definitions,\n} from \"./expand.js\";\nimport { formatHtml } from \"./format.js\";\nimport {\n ATTR_PAGE,\n ATTR_PAGE_TITLE,\n ATTR_QUERY,\n ATTR_REQUEST,\n ATTR_SHOW,\n ATTR_URL_LINK,\n ATTR_URL_PUSH,\n ATTR_URL_UNKNOWN,\n DEVELOPER_ATTRIBUTES,\n HUB_TAG_NAME,\n LB_RESERVED_PREFIX,\n} from \"../core/lb-constants.js\";\nimport { isRowRequest } from \"../core/lb-types.js\";\nimport type { PageFile } from \"./locations.js\";\nimport type { OriginScan } from \"./origins.js\";\n\n/**\n * The hub calls showModal() on the `<dialog>` carrying lb-url-unknown\n * (hub/lb-hub.browser.ts), and looks for it inside itself, as it does for\n * everything else it acts on. A dialog elsewhere in the body would never open\n * and would say nothing about why.\n */\nfunction checkUnknownPageDialog(\n chromeFile: string,\n expandedBody: string,\n): void {\n const fragment = JSDOM.fragment(expandedBody);\n const el = fragment.querySelector(`[${ATTR_URL_UNKNOWN}]`);\n if (!el) return;\n if (!el.closest(HUB_TAG_NAME)) {\n throw new Error(\n `assemble: '${chromeFile}' declares ${ATTR_URL_UNKNOWN} outside ` +\n `<${HUB_TAG_NAME}>, where the hub cannot reach it`,\n );\n }\n}\n\n/**\n * Where the markup stands while the check walks it: inside some `lb-query`,\n * and whether there is a row there to take a value from. Inside a row\n * template there always is. Inside an `lb-query` outside one, there is when\n * the query is of kind `row`, which only the server knows.\n */\ninterface Scope {\n row: boolean | \"unknown\";\n}\n\n/**\n * The markup, checked once against the finished document rather than\n * discovered one console error at a time in a browser.\n *\n * Every rule here is a fact the markup states about itself. The developer\n * writes `lb-*` attributes in markup, and script never assigns them, which is\n * what makes checking the markup sound. See docs/reference/data-binding.md.\n *\n * A `<template>`'s children are walked in the scope that encloses the\n * template, because a row template's clones land in the element around it.\n */\nfunction walkMarkup(\n node: ParentNode,\n scope: Scope | null,\n problems: string[],\n): void {\n for (const el of node.children) {\n const tag = el.localName;\n let here = scope;\n if (el.hasAttribute(ATTR_QUERY)) here = { row: \"unknown\" };\n\n for (const { name } of el.attributes) {\n if (\n name.startsWith(LB_RESERVED_PREFIX) &&\n !(DEVELOPER_ATTRIBUTES as readonly string[]).includes(name)\n ) {\n problems.push(\n `<${tag}> carries ${name}, which is not an attribute a developer ` +\n `writes`,\n );\n }\n }\n\n const request = el.getAttribute(ATTR_REQUEST);\n if (\n request !== null &&\n request.startsWith(LB_RESERVED_PREFIX) &&\n !isRowRequest(request)\n ) {\n problems.push(\n `<${tag}> carries ${ATTR_REQUEST}=\"${request}\", which begins with ` +\n `'${LB_RESERVED_PREFIX}' but names no request Loadbare provides`,\n );\n }\n if (el.hasAttribute(ATTR_URL_LINK) && tag !== \"a\") {\n problems.push(`<${tag}> carries ${ATTR_URL_LINK}; it goes on an <a>`);\n }\n if (el.hasAttribute(ATTR_URL_PUSH) && request === null) {\n problems.push(\n `<${tag}> carries ${ATTR_URL_PUSH} and no ${ATTR_REQUEST}; it goes ` +\n `on an element with ${ATTR_REQUEST}`,\n );\n }\n if (el.hasAttribute(ATTR_URL_UNKNOWN) && tag !== \"dialog\") {\n problems.push(\n `<${tag}> carries ${ATTR_URL_UNKNOWN}; it goes on a <dialog>`,\n );\n }\n\n // Judged by the scope around the element, not one the element carries:\n // its condition is a column of the row around it. With no lb-query\n // around it at all, nothing ever lands on it, and the element never\n // shows.\n const show = el.getAttribute(ATTR_SHOW);\n if (show !== null) {\n if (tag === \"template\") {\n problems.push(\n `a <template> carries ${ATTR_SHOW}=\"${show}\"; write it on the ` +\n `element it shows, and Loadbare supplies the template`,\n );\n } else if (scope === null || scope.row === false) {\n problems.push(\n `<${tag}> carries ${ATTR_SHOW}=\"${show}\" with no row around it ` +\n `to take the value from`,\n );\n }\n }\n\n walkMarkup(el, here, problems);\n // By tag name rather than by instanceof: JSDOM.fragment() parses into a\n // document with no window, so the constructor is not reachable from the\n // node.\n if (tag === \"template\") {\n const content = (el as HTMLTemplateElement).content;\n const isRowTemplate =\n scope !== null && !!el.parentElement?.closest(`[${ATTR_QUERY}]`);\n if (isRowTemplate) {\n for (const root of content.children) {\n if (root.hasAttribute(ATTR_SHOW)) {\n problems.push(\n `<${root.localName}> is a row template's root and carries ` +\n `${ATTR_SHOW}; a row that should not show is left out by ` +\n `the query`,\n );\n }\n }\n }\n walkMarkup(content, isRowTemplate ? { row: true } : here, problems);\n }\n }\n}\n\n/**\n * Ship every `lb-show` element already inside the template the hub keeps an\n * absent branch in, so nothing conditional shows until its value says so.\n * A page is in the document before its data arrives, and a value that has not\n * arrived is no value.\n *\n * Parsed as template content, which accepts a `<tr>` or an `<option>` at the\n * top level where a `<div>` would drop it. Elements are collected before any\n * is moved, and a wrapped element is still reached once its outer one has\n * gone into a template, because the references are held.\n */\nfunction wrapShown(html: string): string {\n const doc = new JSDOM(\"\").window.document;\n const holder = doc.createElement(\"template\");\n holder.innerHTML = html;\n const shown: Element[] = [];\n const collect = (root: ParentNode): void => {\n shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));\n for (const t of root.querySelectorAll(\"template\")) {\n collect((t as HTMLTemplateElement).content);\n }\n };\n collect(holder.content);\n if (shown.length === 0) return html;\n for (const el of shown) {\n const parked = doc.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\n }\n return holder.innerHTML;\n}\n\n/** Check one expanded fragment, and name the file if anything is wrong. */\nfunction checkMarkup(file: string, html: string): void {\n const problems: string[] = [];\n walkMarkup(JSDOM.fragment(html), null, problems);\n if (problems.length > 0) {\n throw new Error(`assemble: '${file}' markup:\\n ` + problems.join(`\\n `));\n }\n}\n\n/**\n * A page file's `<title>` names the page. It is lifted out, since a page is\n * a fragment inside `<main>` and a `<title>` belongs in a document's head,\n * and its text content is stamped on the page as ATTR_PAGE_TITLE.\n */\nfunction liftTitle(html: string): { html: string; title: string | null } {\n const doc = new JSDOM(\"\").window.document;\n const holder = doc.createElement(\"template\");\n holder.innerHTML = html;\n const title = holder.content.querySelector(\"title\");\n if (!title) return { html, title: null };\n const text = title.textContent?.trim() ?? \"\";\n title.remove();\n return { html: holder.innerHTML, title: text };\n}\n\n/** An attribute value, escaped for a double-quoted attribute. */\nfunction attributeValue(text: string): string {\n return text.replace(/&/g, \"&\").replace(/\"/g, \""\");\n}\n\nexport interface AssembleOptions {\n chromeFile: string;\n pages: PageFile[];\n /** Every origin, already scanned, in cascade order — see build/origins.ts. */\n scans: OriginScan[];\n}\n\nexport interface Assembled {\n html: string;\n /** Every custom element tag left in the document once expansion is done. */\n usedTags: Set<string>;\n}\n\nexport async function assemble({\n chromeFile,\n pages,\n scans,\n}: AssembleOptions): Promise<Assembled> {\n // One layer per origin, in order, so a later origin's definition overrides\n // an earlier one's for the same tag. Each layer is checked for cycles as it\n // lands, against everything merged so far.\n let defs: Definitions = {};\n for (const scan of scans) {\n defs = await addDefinitions(defs, scan.definitions);\n }\n\n const templates: string[] = [];\n for (const { name, file } of pages) {\n const authored = await readFile(file, \"utf-8\");\n const { html: host, title } = liftTitle(expand(authored, defs));\n checkMarkup(file, host);\n const titled =\n title === null ? \"\" : ` ${ATTR_PAGE_TITLE}=\"${attributeValue(title)}\"`;\n templates.push(\n `<template ${ATTR_PAGE}=\"${name}\"${titled}>${wrapShown(host)}</template>`,\n );\n }\n\n let chromeSource: string;\n try {\n chromeSource = await readFile(chromeFile, \"utf-8\");\n } catch {\n throw new Error(`assemble: no chrome file at '${chromeFile}'`);\n }\n\n const body = /<body([^>]*)>([\\s\\S]*)<\\/body>/i.exec(chromeSource);\n if (!body) {\n throw new Error(\n `assemble: '${chromeFile}' has no <body>...</body> to expand`,\n );\n }\n const [whole, bodyAttrs, bodyContent] = body;\n const expandedBody = expand(bodyContent!, defs);\n checkUnknownPageDialog(chromeFile, expandedBody);\n checkMarkup(chromeFile, expandedBody);\n const shippedBody = wrapShown(expandedBody);\n const withPages = chromeSource.replace(\n whole!,\n () => `<body${bodyAttrs}>${shippedBody}\\n${templates.join(\"\\n\")}\\n</body>`,\n );\n\n return {\n html: await formatHtml(withPages),\n usedTags: customElementTags(withPages),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Assembling one document out of a chrome file and a set of pages.\n *\n * Every `.html` file in play — the chrome and each page — goes through the\n * same `expand()` (see build/expand.ts) against the same definitions: the\n * built-in widgets and whatever definitions were found in the caller's own\n * source tree. `expand()` only ever sees a fragment, so the chrome's own is\n * the text between its `<body>` tags; a page's is the whole file. A page's\n * expanded host is wrapped in `<template lb-page=\"<name>\">` and, together\n * with the chrome's own expanded body content, spliced back into the\n * chrome's `<body>`. The result is the one document a server has to serve\n * for every route — see docs/tutorials/000-getting-started.md and\n * docs/reference/builder.md.\n *\n * Every path here is a parameter, resolved once by build/locations.ts — this\n * module never reconstructs one of its own, and never reads a directory\n * looking for pages or widgets; it is handed exactly which files those are.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport {\n addDefinitions,\n customElementTags,\n expandTree,\n type Definitions,\n} from \"./expand.js\";\nimport { formatHtml } from \"./format.js\";\nimport {\n ATTR_PAGE,\n ATTR_PAGE_TITLE,\n ATTR_QUERY,\n ATTR_REQUEST,\n ATTR_SHOW,\n ATTR_URL_LINK,\n ATTR_URL_PUSH,\n ATTR_URL_UNKNOWN,\n DEVELOPER_ATTRIBUTES,\n HUB_TAG_NAME,\n LB_RESERVED_PREFIX,\n} from \"../core/lb-constants.js\";\nimport { isRowRequest } from \"../core/lb-types.js\";\nimport type { PageFile } from \"./locations.js\";\nimport type { OriginScan } from \"./origins.js\";\n\n/**\n * The hub calls showModal() on the `<dialog>` carrying lb-url-unknown\n * (hub/lb-hub.browser.ts), and looks for it inside itself, as it does for\n * everything else it acts on. A dialog elsewhere in the body would never open\n * and would say nothing about why.\n */\nfunction checkUnknownPageDialog(chromeFile: string, body: ParentNode): void {\n const el = body.querySelector(`[${ATTR_URL_UNKNOWN}]`);\n if (!el) return;\n if (!el.closest(HUB_TAG_NAME)) {\n throw new Error(\n `assemble: '${chromeFile}' declares ${ATTR_URL_UNKNOWN} outside ` +\n `<${HUB_TAG_NAME}>, where the hub cannot reach it`,\n );\n }\n}\n\n/**\n * Where the markup stands while the check walks it: inside some `lb-query`,\n * and whether there is a row there to take a value from. Inside a row\n * template there always is. Inside an `lb-query` outside one, there is when\n * the query is of kind `row`, which only the server knows.\n */\ninterface Scope {\n row: boolean | \"unknown\";\n}\n\n/**\n * The markup, checked once against the finished document rather than\n * discovered one console error at a time in a browser.\n *\n * Every rule here is a fact the markup states about itself. The developer\n * writes `lb-*` attributes in markup, and script never assigns them, which is\n * what makes checking the markup sound. See docs/reference/data-binding.md.\n *\n * A `<template>`'s children are walked in the scope that encloses the\n * template, because a row template's clones land in the element around it.\n */\nfunction walkMarkup(\n node: ParentNode,\n scope: Scope | null,\n problems: string[],\n): void {\n for (const el of node.children) {\n const tag = el.localName;\n let here = scope;\n if (el.hasAttribute(ATTR_QUERY)) here = { row: \"unknown\" };\n\n for (const { name } of el.attributes) {\n if (\n name.startsWith(LB_RESERVED_PREFIX) &&\n !(DEVELOPER_ATTRIBUTES as readonly string[]).includes(name)\n ) {\n problems.push(\n `<${tag}> carries ${name}, which is not an attribute a developer ` +\n `writes`,\n );\n }\n }\n\n const request = el.getAttribute(ATTR_REQUEST);\n if (\n request !== null &&\n request.startsWith(LB_RESERVED_PREFIX) &&\n !isRowRequest(request)\n ) {\n problems.push(\n `<${tag}> carries ${ATTR_REQUEST}=\"${request}\", which begins with ` +\n `'${LB_RESERVED_PREFIX}' but names no request Loadbare provides`,\n );\n }\n if (el.hasAttribute(ATTR_URL_LINK) && tag !== \"a\") {\n problems.push(`<${tag}> carries ${ATTR_URL_LINK}; it goes on an <a>`);\n }\n if (el.hasAttribute(ATTR_URL_PUSH) && request === null) {\n problems.push(\n `<${tag}> carries ${ATTR_URL_PUSH} and no ${ATTR_REQUEST}; it goes ` +\n `on an element with ${ATTR_REQUEST}`,\n );\n }\n if (el.hasAttribute(ATTR_URL_UNKNOWN) && tag !== \"dialog\") {\n problems.push(\n `<${tag}> carries ${ATTR_URL_UNKNOWN}; it goes on a <dialog>`,\n );\n }\n\n // Judged by the scope around the element, not one the element carries:\n // its condition is a column of the row around it. With no lb-query\n // around it at all, nothing ever lands on it, and the element never\n // shows.\n const show = el.getAttribute(ATTR_SHOW);\n if (show !== null) {\n if (tag === \"template\") {\n problems.push(\n `a <template> carries ${ATTR_SHOW}=\"${show}\"; write it on the ` +\n `element it shows, and Loadbare supplies the template`,\n );\n } else if (scope === null || scope.row === false) {\n problems.push(\n `<${tag}> carries ${ATTR_SHOW}=\"${show}\" with no row around it ` +\n `to take the value from`,\n );\n }\n }\n\n walkMarkup(el, here, problems);\n // By tag name rather than by instanceof: template content belongs to a\n // document with no window, so the constructor is not reachable from the\n // node.\n if (tag === \"template\") {\n const content = (el as HTMLTemplateElement).content;\n const isRowTemplate =\n scope !== null && !!el.parentElement?.closest(`[${ATTR_QUERY}]`);\n if (isRowTemplate) {\n for (const root of content.children) {\n if (root.hasAttribute(ATTR_SHOW)) {\n problems.push(\n `<${root.localName}> is a row template's root and carries ` +\n `${ATTR_SHOW}; a row that should not show is left out by ` +\n `the query`,\n );\n }\n }\n }\n walkMarkup(content, isRowTemplate ? { row: true } : here, problems);\n }\n }\n}\n\n/**\n * Ship every `lb-show` element already inside the template the hub keeps an\n * absent branch in, so nothing conditional shows until its value says so.\n * A page is in the document before its data arrives, and a value that has not\n * arrived is no value.\n *\n * Elements are collected before any is moved, and a wrapped element is still\n * reached once its outer one has gone into a template, because the references\n * are held.\n */\nfunction wrapShown(holder: HTMLTemplateElement): void {\n const doc = holder.ownerDocument;\n const shown: Element[] = [];\n const collect = (root: ParentNode): void => {\n shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));\n for (const t of root.querySelectorAll(\"template\")) {\n collect((t as HTMLTemplateElement).content);\n }\n };\n collect(holder.content);\n for (const el of shown) {\n const parked = doc.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\n }\n}\n\n/**\n * Expand one file's markup, naming the file if expansion refuses it.\n *\n * What comes back is the tree the browser will build, so every pass after\n * this reads and changes it in place, and it is serialized once, when it\n * ships.\n */\nfunction expandFile(\n file: string,\n source: string,\n defs: Definitions,\n): HTMLTemplateElement {\n try {\n return expandTree(source, defs);\n } catch (e) {\n throw new Error(`assemble: '${file}': ${(e as Error).message}`);\n }\n}\n\n/** Check one expanded fragment, and name the file if anything is wrong. */\nfunction checkMarkup(file: string, root: ParentNode): void {\n const problems: string[] = [];\n walkMarkup(root, null, problems);\n if (problems.length > 0) {\n throw new Error(`assemble: '${file}' markup:\\n ` + problems.join(`\\n `));\n }\n}\n\n/**\n * A page file's `<title>` names the page. It is lifted out, since a page is\n * a fragment inside `<main>` and a `<title>` belongs in a document's head,\n * and its text content is stamped on the page as ATTR_PAGE_TITLE.\n */\nfunction liftTitle(holder: HTMLTemplateElement): string | null {\n const title = holder.content.querySelector(\"title\");\n if (!title) return null;\n const text = title.textContent?.trim() ?? \"\";\n title.remove();\n return text;\n}\n\n/** An attribute value, escaped for a double-quoted attribute. */\nfunction attributeValue(text: string): string {\n return text.replace(/&/g, \"&\").replace(/\"/g, \""\");\n}\n\nexport interface AssembleOptions {\n chromeFile: string;\n pages: PageFile[];\n /** Every origin, already scanned, in cascade order — see build/origins.ts. */\n scans: OriginScan[];\n}\n\nexport interface Assembled {\n html: string;\n /** Every custom element tag left in the document once expansion is done. */\n usedTags: Set<string>;\n}\n\nexport async function assemble({\n chromeFile,\n pages,\n scans,\n}: AssembleOptions): Promise<Assembled> {\n // One layer per origin, in order, so a later origin's definition overrides\n // an earlier one's for the same tag. Each layer is checked for cycles as it\n // lands, against everything merged so far.\n let defs: Definitions = {};\n for (const scan of scans) {\n defs = await addDefinitions(defs, scan.definitions);\n }\n\n // Tags are collected from each tree as it goes by. What ships is these\n // trees and the chrome around its body, which is collected on its own.\n const usedTags = new Set<string>();\n const templates: string[] = [];\n for (const { name, file } of pages) {\n const authored = await readFile(file, \"utf-8\");\n const host = expandFile(file, authored, defs);\n const title = liftTitle(host);\n checkMarkup(file, host.content);\n wrapShown(host);\n customElementTags(host.content, usedTags);\n const titled =\n title === null ? \"\" : ` ${ATTR_PAGE_TITLE}=\"${attributeValue(title)}\"`;\n templates.push(\n `<template ${ATTR_PAGE}=\"${name}\"${titled}>${host.innerHTML}</template>`,\n );\n }\n\n let chromeSource: string;\n try {\n chromeSource = await readFile(chromeFile, \"utf-8\");\n } catch {\n throw new Error(`assemble: no chrome file at '${chromeFile}'`);\n }\n\n const body = /<body([^>]*)>([\\s\\S]*)<\\/body>/i.exec(chromeSource);\n if (!body) {\n throw new Error(\n `assemble: '${chromeFile}' has no <body>...</body> to expand`,\n );\n }\n const [whole, bodyAttrs, bodyContent] = body;\n const chromeBody = expandFile(chromeFile, bodyContent!, defs);\n checkUnknownPageDialog(chromeFile, chromeBody.content);\n checkMarkup(chromeFile, chromeBody.content);\n wrapShown(chromeBody);\n customElementTags(chromeBody.content, usedTags);\n customElementTags(chromeSource.replace(whole!, \"\"), usedTags);\n const withPages = chromeSource.replace(\n whole!,\n () =>\n `<body${bodyAttrs}>${chromeBody.innerHTML}\\n${templates.join(\"\\n\")}\\n</body>`,\n );\n\n return { html: await formatHtml(withPages), usedTags };\n}\n"]}
|
package/dist/build/expand.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare function addDefinitions(defs: Definitions, files: Map<string, str
|
|
|
34
34
|
* hyphen in the tag name is the one thing a custom element is guaranteed to
|
|
35
35
|
* have, per the Custom Elements spec, and nothing native has one.
|
|
36
36
|
*/
|
|
37
|
-
export declare function customElementTags(
|
|
37
|
+
export declare function customElementTags(from: string | ParentNode, tags?: Set<string>): Set<string>;
|
|
38
38
|
/**
|
|
39
39
|
* Expand every widget tag in an authored page. Throws rather than shipping.
|
|
40
40
|
*
|
|
@@ -42,4 +42,9 @@ export declare function customElementTags(html: string): Set<string>;
|
|
|
42
42
|
* checked for cycles. Handed a cyclic map directly, this recurses forever.
|
|
43
43
|
*/
|
|
44
44
|
export declare function expand(source: string, defs: Definitions): string;
|
|
45
|
+
/**
|
|
46
|
+
* The same, as the tree the browser builds from it: a template, whose content
|
|
47
|
+
* the builder's later passes read and change before serializing it once.
|
|
48
|
+
*/
|
|
49
|
+
export declare function expandTree(source: string, defs: Definitions): HTMLTemplateElement;
|
|
45
50
|
//# sourceMappingURL=expand.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkCH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkCH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA8CjD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB1E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,WAAW,CAAC,CAStB;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,IAAI,cAAoB,GACvB,GAAG,CAAC,MAAM,CAAC,CAQb;AA+OD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,GAChB,mBAAmB,CAIrB"}
|
package/dist/build/expand.js
CHANGED
|
@@ -45,6 +45,20 @@ function parse(source) {
|
|
|
45
45
|
holder.innerHTML = source;
|
|
46
46
|
return holder;
|
|
47
47
|
}
|
|
48
|
+
const parsedDefinitions = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* A definition's tree, parsed once however many times it is checked or used.
|
|
51
|
+
* Keyed by the text, so an edited definition is a new entry. Read it, or
|
|
52
|
+
* clone it before changing anything.
|
|
53
|
+
*/
|
|
54
|
+
function parseDefinition(source) {
|
|
55
|
+
let tree = parsedDefinitions.get(source);
|
|
56
|
+
if (tree === undefined) {
|
|
57
|
+
tree = parse(source);
|
|
58
|
+
parsedDefinitions.set(source, tree);
|
|
59
|
+
}
|
|
60
|
+
return tree;
|
|
61
|
+
}
|
|
48
62
|
/**
|
|
49
63
|
* A tree and the content of every template in it, however deep.
|
|
50
64
|
*
|
|
@@ -109,7 +123,7 @@ export async function addDefinitions(defs, files) {
|
|
|
109
123
|
/** Every placeholder a definition declares. */
|
|
110
124
|
function placeholdersOf(source) {
|
|
111
125
|
const names = [];
|
|
112
|
-
for (const scope of scopes(
|
|
126
|
+
for (const scope of scopes(parseDefinition(source))) {
|
|
113
127
|
for (const el of scope.querySelectorAll("*")) {
|
|
114
128
|
for (const attr of el.attributes) {
|
|
115
129
|
const match = PLACEHOLDER.exec(attr.value.trim());
|
|
@@ -157,9 +171,8 @@ function checkNames(file, source) {
|
|
|
157
171
|
* hyphen in the tag name is the one thing a custom element is guaranteed to
|
|
158
172
|
* have, per the Custom Elements spec, and nothing native has one.
|
|
159
173
|
*/
|
|
160
|
-
export function customElementTags(
|
|
161
|
-
const
|
|
162
|
-
for (const scope of scopes(parse(html))) {
|
|
174
|
+
export function customElementTags(from, tags = new Set()) {
|
|
175
|
+
for (const scope of scopes(typeof from === "string" ? parse(from) : from)) {
|
|
163
176
|
for (const el of scope.querySelectorAll("*")) {
|
|
164
177
|
const tag = el.tagName.toLowerCase();
|
|
165
178
|
if (tag.includes("-"))
|
|
@@ -171,7 +184,7 @@ export function customElementTags(html) {
|
|
|
171
184
|
/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */
|
|
172
185
|
function referencesOf(source, defs) {
|
|
173
186
|
const refs = new Set();
|
|
174
|
-
for (const scope of scopes(
|
|
187
|
+
for (const scope of scopes(parseDefinition(source))) {
|
|
175
188
|
for (const el of scope.querySelectorAll("*")) {
|
|
176
189
|
const tag = el.tagName.toUpperCase();
|
|
177
190
|
if (CUSTOM_ELEMENT.test(tag) && tag in defs)
|
|
@@ -336,7 +349,7 @@ function expandElement(el, defs, source) {
|
|
|
336
349
|
continue;
|
|
337
350
|
attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);
|
|
338
351
|
}
|
|
339
|
-
const tree =
|
|
352
|
+
const tree = parseDefinition(source).cloneNode(true);
|
|
340
353
|
const asked = new Set();
|
|
341
354
|
substitute(tree, attrs, asked);
|
|
342
355
|
// A parameter the definition never declared is a name that resolves to
|
|
@@ -377,8 +390,80 @@ function walk(root, defs) {
|
|
|
377
390
|
* checked for cycles. Handed a cyclic map directly, this recurses forever.
|
|
378
391
|
*/
|
|
379
392
|
export function expand(source, defs) {
|
|
393
|
+
return expandTree(source, defs).innerHTML;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The same, as the tree the browser builds from it: a template, whose content
|
|
397
|
+
* the builder's later passes read and change before serializing it once.
|
|
398
|
+
*/
|
|
399
|
+
export function expandTree(source, defs) {
|
|
380
400
|
const tree = parse(source);
|
|
381
401
|
walk(tree, defs);
|
|
382
|
-
return tree
|
|
402
|
+
return parsesBack(tree);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The tree the browser builds from the expanded tree's text, provided it is
|
|
406
|
+
* the same tree.
|
|
407
|
+
*
|
|
408
|
+
* Each file was parsed on its own, so everything inside one file already has
|
|
409
|
+
* the shape the parser gives it. Expansion then moves one file's nodes into
|
|
410
|
+
* another's through the DOM, which applies none of HTML's rules for what an
|
|
411
|
+
* element may hold. Where two files meet, the tree can hold what no parse
|
|
412
|
+
* would build: a definition's `<dialog>` inside a page's `<p>`. The browser
|
|
413
|
+
* reads the text and closes the `<p>` early, and the dialog lands outside its
|
|
414
|
+
* element with nothing logged.
|
|
415
|
+
*
|
|
416
|
+
* Reparsed in a template, the context a page ships in. Only a text that
|
|
417
|
+
* differs is walked, to name what moved.
|
|
418
|
+
*/
|
|
419
|
+
function parsesBack(tree) {
|
|
420
|
+
const html = tree.innerHTML;
|
|
421
|
+
const reparsed = doc.createElement("template");
|
|
422
|
+
reparsed.innerHTML = html;
|
|
423
|
+
if (reparsed.innerHTML === html)
|
|
424
|
+
return reparsed;
|
|
425
|
+
const moved = firstMoved(tree, reparsed.content) ?? tree;
|
|
426
|
+
const path = [];
|
|
427
|
+
let owner = null;
|
|
428
|
+
for (let el = moved; el && el !== tree; el = el.parentNode) {
|
|
429
|
+
if (!(el instanceof Element))
|
|
430
|
+
continue;
|
|
431
|
+
const tag = el.tagName.toLowerCase();
|
|
432
|
+
path.unshift(tag);
|
|
433
|
+
if (owner === null && el !== moved && CUSTOM_ELEMENT.test(tag)) {
|
|
434
|
+
owner = tag;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const what = path.length > 0 ? `<${path.at(-1)}>` : "content";
|
|
438
|
+
const where = path.length > 0 ? path.join(" > ") : "the top level";
|
|
439
|
+
throw new Error(`expand: ${owner ? `<${owner}> puts` : "expansion puts"} ${what} ` +
|
|
440
|
+
`where the browser's parser would not keep it, at ${where}. HTML does ` +
|
|
441
|
+
`not let every element hold every other, and the browser moves what ` +
|
|
442
|
+
`breaks the rule.`);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The first node, in document order, that the reparse does not have in the
|
|
446
|
+
* same place. A node the reparse gained reports its parent.
|
|
447
|
+
*/
|
|
448
|
+
function firstMoved(built, parsed) {
|
|
449
|
+
const ours = built.childNodes;
|
|
450
|
+
const theirs = parsed.childNodes;
|
|
451
|
+
for (let i = 0; i < ours.length; i++) {
|
|
452
|
+
const node = ours[i];
|
|
453
|
+
const other = theirs[i];
|
|
454
|
+
if (!other ||
|
|
455
|
+
node.nodeName !== other.nodeName ||
|
|
456
|
+
(node.nodeType !== Node.ELEMENT_NODE &&
|
|
457
|
+
node.nodeValue !== other.nodeValue)) {
|
|
458
|
+
return node.nodeType === Node.ELEMENT_NODE ? node : built;
|
|
459
|
+
}
|
|
460
|
+
const inner = firstMoved(node, other) ??
|
|
461
|
+
(node instanceof HTMLTemplateElement
|
|
462
|
+
? firstMoved(node.content, other.content)
|
|
463
|
+
: null);
|
|
464
|
+
if (inner)
|
|
465
|
+
return inner;
|
|
466
|
+
}
|
|
467
|
+
return ours.length < theirs.length ? built : null;
|
|
383
468
|
}
|
|
384
469
|
//# sourceMappingURL=expand.js.map
|
package/dist/build/expand.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expansion — see docs/reference/custom-elements.md, \"HTML\".\n *\n * A page author writes one tag and gets the tree it stands for. Expansion\n * runs at build time, before any request exists, so it has no data. That is\n * why there is no control flow here: nothing to branch on.\n *\n * Substitution is applied to a parsed tree with setAttribute and node values,\n * never to a string. A value applied through the DOM API cannot be reparsed\n * as markup, so injection is impossible by construction.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport { ATTR_EXP_SLOT, ATTR_EXP_TEMPLATE } from \"../core/lb-constants.js\";\n\n/** Lowercase, hyphenated — the one shape a custom element tag has. */\nconst CUSTOM_ELEMENT = /-/;\n\n/** A placeholder is an entire attribute value or an entire text node. */\n/**\n * `{{name}}` or `{{name|default}}`. The default is a literal that stands in\n * when the tag supplies nothing; it is not evaluated and nothing branches on\n * it. Whitespace around the name and the default is not part of either, so\n * `{{button-text | OK}}` is the same placeholder as `{{button-text|OK}}` —\n * a spaced form that shipped as literal braces would be a silent failure\n * an author would find in the browser.\n */\nconst PLACEHOLDER = /^\\{\\{\\s*([A-Za-z][\\w-]*)\\s*(?:\\|\\s*([^}]*?)\\s*)?\\}\\}$/;\n\n/**\n * Marks an attribute as input to expansion. Three namespaces share the tag:\n * `lb-` holds the names Loadbare defines, `exp-` the names an element file\n * defines, and everything unprefixed is HTML's and means what HTML says it\n * means. The split is by owner rather than by phase — `lb-exp-slot` and\n * `lb-exp-template` are expansion's own and never reach the browser, while\n * `lb-query` and `lb-column` are read at both times. So an author is free to write\n * `class`, `title` or `hidden` on any widget, whatever its parameters happen\n * to be named. The prefix is needed on the tag, where a parameter and an\n * HTML attribute would otherwise share one namespace; inside a definition\n * `{{ }}` already marks a parameter, so `exp-label` fills `{{label}}`.\n */\nconst EXP_PREFIX = \"exp-\";\n\nexport type Definitions = Record<string, string>;\n\nconst dom = new JSDOM(\"\");\nconst doc = dom.window.document;\nconst { Element, HTMLTemplateElement, Node } = dom.window;\n\nfunction parse(source: string): HTMLElement {\n const holder = doc.createElement(\"div\");\n holder.innerHTML = source;\n return holder as unknown as HTMLElement;\n}\n\n/**\n * A tree and the content of every template in it, however deep.\n *\n * A template's children are parsed into a fragment rather than into the tree,\n * so `querySelectorAll` does not reach them. Everything expansion does has to\n * reach them anyway: a definition may supply its own row template, and\n * `lb-picker` does, so `{{key}}` written inside one has to be a\n * placeholder rather than literal text that ships. `walk` already descends\n * into template content to expand widget tags; this is the same rule stated\n * once for every pass that reads a tree.\n */\nfunction* scopes(root: ParentNode): Generator<ParentNode> {\n yield root;\n for (const el of root.querySelectorAll(\"template\")) {\n yield* scopes((el as HTMLTemplateElement).content);\n }\n}\n\n/**\n * Load one definition per file, named by the tag it defines.\n *\n * Later directories win, so an application can override a built-in widget.\n */\nexport async function loadDefinitions(dirs: string[]): Promise<Definitions> {\n const defs: Definitions = {};\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith(\".html\")) continue;\n const file = `${dir}/${name}`;\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n defs[name.slice(0, -\".html\".length).toUpperCase()] = source;\n }\n }\n checkAcyclic(defs);\n return defs;\n}\n\n/**\n * Overlay definitions read from explicit files, each already keyed by the\n * tag it defines — for a source, like an application's own `src` tree, that\n * is discovered file by file rather than listed by directory. A tag here\n * overrides the same tag in `defs`, the same override `loadDefinitions`\n * gives a later directory over an earlier one.\n */\nexport async function addDefinitions(\n defs: Definitions,\n files: Map<string, string>,\n): Promise<Definitions> {\n const merged = { ...defs };\n for (const [tag, file] of files) {\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n merged[tag.toUpperCase()] = source;\n }\n checkAcyclic(merged);\n return merged;\n}\n\n/** Every placeholder a definition declares. */\nfunction placeholdersOf(source: string): string[] {\n const names: string[] = [];\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of el.attributes) {\n const match = PLACEHOLDER.exec(attr.value.trim());\n if (match) names.push(match[1]!);\n }\n }\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n while (walker.nextNode()) {\n const match = PLACEHOLDER.exec((walker.currentNode as Text).data.trim());\n if (match) names.push(match[1]!);\n }\n }\n return names;\n}\n\n/**\n * A placeholder must be spelled the way a tag can supply it.\n *\n * HTML lowercases attribute names, so `exp-inputClass` reaches expansion as\n * `exp-inputclass` and could never fill `{{inputClass}}`. A definition is the\n * only file that can hold such a name and the only one that can fix it, so it\n * is rejected here, once, rather than at every page that uses the widget.\n *\n * Recognition stays broad on purpose. Narrowing PLACEHOLDER to lowercase would\n * make `{{inputClass}}` not a placeholder at all, and ship it to the browser as\n * literal text — the same silent failure somewhere new.\n */\nfunction checkNames(file: string, source: string): void {\n for (const name of placeholdersOf(source)) {\n if (!/[A-Z]/.test(name)) continue;\n const kebab = name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n throw new Error(\n `expand: ${file} declares {{${name}}}, but HTML lowercases attribute ` +\n `names, so no tag can supply it. Write {{${kebab}}}.`,\n );\n }\n}\n\n/**\n * Every hyphenated tag name left in a document, template content included.\n *\n * Meant to run on already-expanded output: an `LB-*` tag with a definition\n * is gone by then, replaced by the tree it stands for, so what remains is\n * whatever is left for the browser to upgrade itself — a built-in widget\n * class, an application's own custom element, or one from a package. A\n * hyphen in the tag name is the one thing a custom element is guaranteed to\n * have, per the Custom Elements spec, and nothing native has one.\n */\nexport function customElementTags(html: string): Set<string> {\n const tags = new Set<string>();\n for (const scope of scopes(parse(html))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toLowerCase();\n if (tag.includes(\"-\")) tags.add(tag);\n }\n }\n return tags;\n}\n\n/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */\nfunction referencesOf(source: string, defs: Definitions): string[] {\n const refs = new Set<string>();\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toUpperCase();\n if (CUSTOM_ELEMENT.test(tag) && tag in defs) refs.add(tag);\n }\n }\n return [...refs];\n}\n\n/**\n * Expansion is a fixpoint, so it terminates only on an acyclic graph. A depth\n * cap would hide the bug behind a limit instead of reporting it.\n */\nfunction checkAcyclic(defs: Definitions): void {\n const refs = new Map<string, string[]>();\n for (const [tag, source] of Object.entries(defs)) {\n refs.set(tag, referencesOf(source, defs));\n }\n const done = new Set<string>();\n const path: string[] = [];\n\n const visit = (tag: string): void => {\n if (done.has(tag)) return;\n const cycle = path.indexOf(tag);\n if (cycle !== -1) {\n throw new Error(\n `expand: cycle in the definition graph: ` +\n [...path.slice(cycle), tag].map((t) => t.toLowerCase()).join(\" → \"),\n );\n }\n path.push(tag);\n for (const ref of refs.get(tag) ?? []) visit(ref);\n path.pop();\n done.add(tag);\n };\n\n for (const tag of refs.keys()) visit(tag);\n}\n\n/**\n * Resolve a placeholder against what the author wrote: the supplied value,\n * else the definition's default, else absent.\n *\n * Every name the definition asks for is recorded, whether or not it was\n * supplied, so expansion can report a parameter the definition never declared.\n */\nfunction resolve(\n text: string,\n attrs: Map<string, string>,\n asked: Set<string>,\n): string | null {\n const match = PLACEHOLDER.exec(text.trim());\n if (!match) return text;\n const name = match[1]!;\n asked.add(name);\n return attrs.get(name) ?? match[2] ?? null;\n}\n\nfunction substitute(\n root: ParentNode,\n attrs: Map<string, string>,\n asked: Set<string>,\n): void {\n for (const scope of scopes(root)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n const value = resolve(attr.value, attrs, asked);\n // Presence propagates: a placeholder with nothing behind it drops the\n // attribute rather than emitting it empty, which is what HTML's own\n // boolean-attribute semantics require.\n if (value === null) el.removeAttribute(attr.name);\n else if (value !== attr.value) el.setAttribute(attr.name, value);\n }\n }\n\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n const texts: Text[] = [];\n while (walker.nextNode()) texts.push(walker.currentNode as Text);\n for (const text of texts) {\n const raw = text.data;\n if (!PLACEHOLDER.test(raw.trim())) continue;\n const value = resolve(raw, attrs, asked) ?? \"\";\n // Keep the author's surrounding whitespace: \"{{label}} <input>\" should\n // still separate the label from the control.\n const lead = raw.slice(0, raw.length - raw.trimStart().length);\n const tail = raw.slice(raw.trimEnd().length);\n text.data = lead + value + tail;\n }\n }\n}\n\n/**\n * Fill the definition's named destinations from the authored templates that\n * name them, and hand back the content that is left for the slot.\n *\n * A destination is filled with the template's contents, not with the\n * template, so what ships is ordinary markup in an ordinary place — a real\n * `<tr>` inside a real `<thead>`, visible in view-source like everything\n * else. The wrapper existed only to survive the parser and is gone.\n *\n * Unlike the slot, there may be several, because the reason for wrapping is\n * per-destination: a table has a head and a body, and each needs its own\n * markup past the same obstacle. They are matched by name, and an empty name\n * is a name, so a widget with one destination costs the author nothing.\n */\nfunction fillTemplates(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): globalThis.Node[] {\n const destinations = new Map<string, Element>();\n for (const el of root.querySelectorAll(`[${ATTR_EXP_TEMPLATE}]`)) {\n const name = el.getAttribute(ATTR_EXP_TEMPLATE)!;\n if (destinations.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares two ${ATTR_EXP_TEMPLATE}=\"${name}\" destinations`,\n );\n }\n destinations.set(name, el);\n el.removeAttribute(ATTR_EXP_TEMPLATE);\n }\n\n const rest: globalThis.Node[] = [];\n const filled = new Set<string>();\n for (const node of content) {\n const named =\n node instanceof HTMLTemplateElement &&\n node.hasAttribute(ATTR_EXP_TEMPLATE);\n if (!named) {\n rest.push(node);\n continue;\n }\n const template = node as HTMLTemplateElement;\n const name = template.getAttribute(ATTR_EXP_TEMPLATE)!;\n const destination = destinations.get(name);\n if (!destination) {\n throw new Error(\n `expand: a <template ${ATTR_EXP_TEMPLATE}=\"${name}\"> was written inside ` +\n `<${tag.toLowerCase()}>, whose definition has no destination by that name.`,\n );\n }\n if (filled.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given two templates for '${name}'`,\n );\n }\n filled.add(name);\n destination.append(template.content);\n }\n return rest;\n}\n\n/** Place authored inner content in the single slot, if the definition has one. */\nfunction fillSlot(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): void {\n const slots = root.querySelectorAll(`[${ATTR_EXP_SLOT}]`);\n if (slots.length > 1) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares more than one ${ATTR_EXP_SLOT}`,\n );\n }\n if (slots.length === 0) {\n // Whitespace is not content. An author who writes only named templates\n // still leaves the newlines between them, and those are the file's\n // formatting rather than something anybody meant to place.\n const wrote = content.some(\n (node) => node.nodeType !== Node.TEXT_NODE || (node as Text).data.trim(),\n );\n if (wrote) {\n throw new Error(\n `expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_EXP_SLOT}`,\n );\n }\n return;\n }\n const slot = slots[0]!;\n // The authored nodes move rather than copy. The caller replaces the tag's\n // children with the expanded tree immediately after, so there is nothing\n // left behind for a clone to protect.\n slot.append(...content);\n slot.removeAttribute(ATTR_EXP_SLOT);\n}\n\nfunction expandElement(el: Element, defs: Definitions, source: string): void {\n const tag = el.tagName.toUpperCase();\n\n // The tag survives expansion carrying every attribute the author wrote,\n // `exp-` ones included. They cost nothing where they are, and what ships\n // shows what was asked for beside what it produced.\n const attrs = new Map<string, string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(EXP_PREFIX)) continue;\n attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);\n }\n\n const tree = parse(source);\n const asked = new Set<string>();\n substitute(tree, attrs, asked);\n\n // A parameter the definition never declared is a name that resolves to\n // nothing. Unprefixed attributes could not support this check, because a\n // stray one is indistinguishable from a deliberate HTML attribute.\n for (const name of attrs.keys()) {\n if (asked.has(name)) continue;\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given ${EXP_PREFIX}${name}, ` +\n `but its definition has no {{${name}}}.`,\n );\n }\n // Named destinations first: what they take is addressed to them, and only\n // what is left is the slot's.\n fillSlot(tag, tree, fillTemplates(tag, tree, [...el.childNodes]));\n el.replaceChildren(...tree.childNodes);\n}\n\nfunction walk(root: ParentNode, defs: Definitions): void {\n for (const child of [...root.children]) {\n const source = defs[child.tagName.toUpperCase()];\n // A tag with no definition is left alone. It is a custom element a\n // script registers, and build/elements.ts is where a tag that has\n // neither a definition nor a script is refused — one rule, one place.\n if (source !== undefined) expandElement(child, defs, source);\n // Definitions may use other widgets, so descend after expanding. The\n // acyclicity check is what makes this terminate.\n walk(child, defs);\n // Template content is markup in a file like any other, and it is inert,\n // so a row template ships already expanded.\n if (child instanceof HTMLTemplateElement) walk(child.content, defs);\n }\n}\n\n/**\n * Expand every widget tag in an authored page. Throws rather than shipping.\n *\n * Definitions must come from `loadDefinitions`, which is where the graph is\n * checked for cycles. Handed a cyclic map directly, this recurses forever.\n */\nexport function expand(source: string, defs: Definitions): string {\n const tree = parse(source);\n walk(tree, defs);\n return tree.innerHTML;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE3E,sEAAsE;AACtE,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,yEAAyE;AACzE;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG,uDAAuD,CAAC;AAE5E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC;AAI1B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;AAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;AAChC,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;AAE1D,SAAS,KAAK,CAAC,MAAc;IAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC;IAC1B,OAAO,MAAgC,CAAC;AAC1C,CAAC;AAED,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEzD;;;;GAIG;AACH,SAAS,eAAe,CAAC,MAAc;IACrC,IAAI,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAgB;IAC/B,MAAM,IAAI,CAAC;IACX,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QACnD,KAAK,CAAC,CAAC,MAAM,CAAE,EAA0B,CAAC,OAAO,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAc;IAClD,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,SAAS;YACtC,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,MAAM,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,YAAY,CAAC,IAAI,CAAC,CAAC;IACnB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAiB,EACjB,KAA0B;IAE1B,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,MAAM,CAAC;IACrC,CAAC;IACD,YAAY,CAAC,MAAM,CAAC,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+CAA+C;AAC/C,SAAS,cAAc,CAAC,MAAc;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACpD,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;gBACjC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAClD,IAAI,KAAK;oBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,KAAwB,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;QAC5E,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAE,MAAM,CAAC,WAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACzE,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,UAAU,CAAC,IAAY,EAAE,MAAc;IAC9C,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,eAAe,IAAI,oCAAoC;YACpE,2CAA2C,KAAK,KAAK,CACxD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAyB,EACzB,OAAO,IAAI,GAAG,EAAU;IAExB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1E,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yGAAyG;AACzG,SAAS,YAAY,CAAC,MAAc,EAAE,IAAiB;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACpD,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,IAAI;gBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,IAAiB;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,MAAM,KAAK,GAAG,CAAC,GAAW,EAAQ,EAAE;QAClC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,yCAAyC;gBACvC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CACtE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,OAAO,CACd,IAAY,EACZ,KAA0B,EAC1B,KAAkB;IAElB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACvB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC7C,CAAC;AAED,SAAS,UAAU,CACjB,IAAgB,EAChB,KAA0B,EAC1B,KAAkB;IAElB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBAChD,sEAAsE;gBACtE,oEAAoE;gBACpE,uCAAuC;gBACvC,IAAI,KAAK,KAAK,IAAI;oBAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;qBAC7C,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK;oBAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,KAAwB,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAW,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,QAAQ,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAAmB,CAAC,CAAC;QACjE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;YACtB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBAAE,SAAS;YAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YAC/C,uEAAuE;YACvE,6CAA6C;YAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC;YAC/D,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;QAClC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,aAAa,CACpB,GAAW,EACX,IAAgB,EAChB,OAA0B;IAE1B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAmB,CAAC;IAChD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAE,CAAC;QACjD,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,CAAC,WAAW,EAAE,kBAAkB,iBAAiB,KAAK,IAAI,gBAAgB,CAC1F,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3B,EAAE,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,IAAI,GAAsB,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,KAAK,GACT,IAAI,YAAY,mBAAmB;YACnC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,IAA2B,CAAC;QAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAC,iBAAiB,CAAE,CAAC;QACvD,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,uBAAuB,iBAAiB,KAAK,IAAI,wBAAwB;gBACvE,IAAI,GAAG,CAAC,WAAW,EAAE,sDAAsD,CAC9E,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,CAAC,WAAW,EAAE,kCAAkC,IAAI,GAAG,CACvE,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjB,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,SAAS,QAAQ,CACf,GAAW,EACX,IAAgB,EAChB,OAA0B;IAE1B,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,CAAC,WAAW,EAAE,4BAA4B,aAAa,EAAE,CACzE,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,uEAAuE;QACvE,mEAAmE;QACnE,2DAA2D;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CACxB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,IAAK,IAAa,CAAC,IAAI,CAAC,IAAI,EAAE,CACzE,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,uCAAuC,GAAG,CAAC,WAAW,EAAE,8BAA8B,aAAa,EAAE,CACtG,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACvB,0EAA0E;IAC1E,yEAAyE;IACzE,sCAAsC;IACtC,IAAI,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC;IACxB,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,aAAa,CAAC,EAAW,EAAE,IAAiB,EAAE,MAAc;IACnE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAErC,wEAAwE;IACxE,yEAAyE;IACzE,oDAAoD;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAChD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,IAAI,CAAgB,CAAC;IACpE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAE/B,uEAAuE;IACvE,yEAAyE;IACzE,mEAAmE;IACnE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9B,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,CAAC,WAAW,EAAE,eAAe,UAAU,GAAG,IAAI,IAAI;YAC/D,+BAA+B,IAAI,KAAK,CAC3C,CAAC;IACJ,CAAC;IACD,0EAA0E;IAC1E,8BAA8B;IAC9B,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClE,EAAE,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,IAAI,CAAC,IAAgB,EAAE,IAAiB;IAC/C,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QACjD,mEAAmE;QACnE,kEAAkE;QAClE,sEAAsE;QACtE,IAAI,MAAM,KAAK,SAAS;YAAE,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7D,qEAAqE;QACrE,iDAAiD;QACjD,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClB,wEAAwE;QACxE,4CAA4C;QAC5C,IAAI,KAAK,YAAY,mBAAmB;YAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,MAAc,EAAE,IAAiB;IACtD,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,IAAiB;IAEjB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,UAAU,CAAC,IAAiB;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;IAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC;IAC1B,IAAI,QAAQ,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,KAAK,IAAI,EAAE,GAAgB,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC;QACxE,IAAI,CAAC,CAAC,EAAE,YAAY,OAAO,CAAC;YAAE,SAAS;QACvC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,KAAK,KAAK,IAAI,IAAI,EAAE,KAAK,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/D,KAAK,GAAG,GAAG,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;IACnE,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,IAAI,IAAI,GAAG;QAChE,oDAAoD,KAAK,cAAc;QACvE,qEAAqE;QACrE,kBAAkB,CACrB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,KAAW,EAAE,MAAY;IAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IACE,CAAC,KAAK;YACN,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;YAChC,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;gBAClC,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC,EACrC,CAAC;YACD,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;QAC5D,CAAC;QACD,MAAM,KAAK,GACT,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC;YACvB,CAAC,IAAI,YAAY,mBAAmB;gBAClC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAG,KAA6B,CAAC,OAAO,CAAC;gBAClE,CAAC,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC","sourcesContent":["/**\n * Expansion — see docs/reference/custom-elements.md, \"HTML\".\n *\n * A page author writes one tag and gets the tree it stands for. Expansion\n * runs at build time, before any request exists, so it has no data. That is\n * why there is no control flow here: nothing to branch on.\n *\n * Substitution is applied to a parsed tree with setAttribute and node values,\n * never to a string. A value applied through the DOM API cannot be reparsed\n * as markup, so injection is impossible by construction.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport { ATTR_EXP_SLOT, ATTR_EXP_TEMPLATE } from \"../core/lb-constants.js\";\n\n/** Lowercase, hyphenated — the one shape a custom element tag has. */\nconst CUSTOM_ELEMENT = /-/;\n\n/** A placeholder is an entire attribute value or an entire text node. */\n/**\n * `{{name}}` or `{{name|default}}`. The default is a literal that stands in\n * when the tag supplies nothing; it is not evaluated and nothing branches on\n * it. Whitespace around the name and the default is not part of either, so\n * `{{button-text | OK}}` is the same placeholder as `{{button-text|OK}}` —\n * a spaced form that shipped as literal braces would be a silent failure\n * an author would find in the browser.\n */\nconst PLACEHOLDER = /^\\{\\{\\s*([A-Za-z][\\w-]*)\\s*(?:\\|\\s*([^}]*?)\\s*)?\\}\\}$/;\n\n/**\n * Marks an attribute as input to expansion. Three namespaces share the tag:\n * `lb-` holds the names Loadbare defines, `exp-` the names an element file\n * defines, and everything unprefixed is HTML's and means what HTML says it\n * means. The split is by owner rather than by phase — `lb-exp-slot` and\n * `lb-exp-template` are expansion's own and never reach the browser, while\n * `lb-query` and `lb-column` are read at both times. So an author is free to write\n * `class`, `title` or `hidden` on any widget, whatever its parameters happen\n * to be named. The prefix is needed on the tag, where a parameter and an\n * HTML attribute would otherwise share one namespace; inside a definition\n * `{{ }}` already marks a parameter, so `exp-label` fills `{{label}}`.\n */\nconst EXP_PREFIX = \"exp-\";\n\nexport type Definitions = Record<string, string>;\n\nconst dom = new JSDOM(\"\");\nconst doc = dom.window.document;\nconst { Element, HTMLTemplateElement, Node } = dom.window;\n\nfunction parse(source: string): HTMLElement {\n const holder = doc.createElement(\"div\");\n holder.innerHTML = source;\n return holder as unknown as HTMLElement;\n}\n\nconst parsedDefinitions = new Map<string, HTMLElement>();\n\n/**\n * A definition's tree, parsed once however many times it is checked or used.\n * Keyed by the text, so an edited definition is a new entry. Read it, or\n * clone it before changing anything.\n */\nfunction parseDefinition(source: string): HTMLElement {\n let tree = parsedDefinitions.get(source);\n if (tree === undefined) {\n tree = parse(source);\n parsedDefinitions.set(source, tree);\n }\n return tree;\n}\n\n/**\n * A tree and the content of every template in it, however deep.\n *\n * A template's children are parsed into a fragment rather than into the tree,\n * so `querySelectorAll` does not reach them. Everything expansion does has to\n * reach them anyway: a definition may supply its own row template, and\n * `lb-picker` does, so `{{key}}` written inside one has to be a\n * placeholder rather than literal text that ships. `walk` already descends\n * into template content to expand widget tags; this is the same rule stated\n * once for every pass that reads a tree.\n */\nfunction* scopes(root: ParentNode): Generator<ParentNode> {\n yield root;\n for (const el of root.querySelectorAll(\"template\")) {\n yield* scopes((el as HTMLTemplateElement).content);\n }\n}\n\n/**\n * Load one definition per file, named by the tag it defines.\n *\n * Later directories win, so an application can override a built-in widget.\n */\nexport async function loadDefinitions(dirs: string[]): Promise<Definitions> {\n const defs: Definitions = {};\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith(\".html\")) continue;\n const file = `${dir}/${name}`;\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n defs[name.slice(0, -\".html\".length).toUpperCase()] = source;\n }\n }\n checkAcyclic(defs);\n return defs;\n}\n\n/**\n * Overlay definitions read from explicit files, each already keyed by the\n * tag it defines — for a source, like an application's own `src` tree, that\n * is discovered file by file rather than listed by directory. A tag here\n * overrides the same tag in `defs`, the same override `loadDefinitions`\n * gives a later directory over an earlier one.\n */\nexport async function addDefinitions(\n defs: Definitions,\n files: Map<string, string>,\n): Promise<Definitions> {\n const merged = { ...defs };\n for (const [tag, file] of files) {\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n merged[tag.toUpperCase()] = source;\n }\n checkAcyclic(merged);\n return merged;\n}\n\n/** Every placeholder a definition declares. */\nfunction placeholdersOf(source: string): string[] {\n const names: string[] = [];\n for (const scope of scopes(parseDefinition(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of el.attributes) {\n const match = PLACEHOLDER.exec(attr.value.trim());\n if (match) names.push(match[1]!);\n }\n }\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n while (walker.nextNode()) {\n const match = PLACEHOLDER.exec((walker.currentNode as Text).data.trim());\n if (match) names.push(match[1]!);\n }\n }\n return names;\n}\n\n/**\n * A placeholder must be spelled the way a tag can supply it.\n *\n * HTML lowercases attribute names, so `exp-inputClass` reaches expansion as\n * `exp-inputclass` and could never fill `{{inputClass}}`. A definition is the\n * only file that can hold such a name and the only one that can fix it, so it\n * is rejected here, once, rather than at every page that uses the widget.\n *\n * Recognition stays broad on purpose. Narrowing PLACEHOLDER to lowercase would\n * make `{{inputClass}}` not a placeholder at all, and ship it to the browser as\n * literal text — the same silent failure somewhere new.\n */\nfunction checkNames(file: string, source: string): void {\n for (const name of placeholdersOf(source)) {\n if (!/[A-Z]/.test(name)) continue;\n const kebab = name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n throw new Error(\n `expand: ${file} declares {{${name}}}, but HTML lowercases attribute ` +\n `names, so no tag can supply it. Write {{${kebab}}}.`,\n );\n }\n}\n\n/**\n * Every hyphenated tag name left in a document, template content included.\n *\n * Meant to run on already-expanded output: an `LB-*` tag with a definition\n * is gone by then, replaced by the tree it stands for, so what remains is\n * whatever is left for the browser to upgrade itself — a built-in widget\n * class, an application's own custom element, or one from a package. A\n * hyphen in the tag name is the one thing a custom element is guaranteed to\n * have, per the Custom Elements spec, and nothing native has one.\n */\nexport function customElementTags(\n from: string | ParentNode,\n tags = new Set<string>(),\n): Set<string> {\n for (const scope of scopes(typeof from === \"string\" ? parse(from) : from)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toLowerCase();\n if (tag.includes(\"-\")) tags.add(tag);\n }\n }\n return tags;\n}\n\n/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */\nfunction referencesOf(source: string, defs: Definitions): string[] {\n const refs = new Set<string>();\n for (const scope of scopes(parseDefinition(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toUpperCase();\n if (CUSTOM_ELEMENT.test(tag) && tag in defs) refs.add(tag);\n }\n }\n return [...refs];\n}\n\n/**\n * Expansion is a fixpoint, so it terminates only on an acyclic graph. A depth\n * cap would hide the bug behind a limit instead of reporting it.\n */\nfunction checkAcyclic(defs: Definitions): void {\n const refs = new Map<string, string[]>();\n for (const [tag, source] of Object.entries(defs)) {\n refs.set(tag, referencesOf(source, defs));\n }\n const done = new Set<string>();\n const path: string[] = [];\n\n const visit = (tag: string): void => {\n if (done.has(tag)) return;\n const cycle = path.indexOf(tag);\n if (cycle !== -1) {\n throw new Error(\n `expand: cycle in the definition graph: ` +\n [...path.slice(cycle), tag].map((t) => t.toLowerCase()).join(\" → \"),\n );\n }\n path.push(tag);\n for (const ref of refs.get(tag) ?? []) visit(ref);\n path.pop();\n done.add(tag);\n };\n\n for (const tag of refs.keys()) visit(tag);\n}\n\n/**\n * Resolve a placeholder against what the author wrote: the supplied value,\n * else the definition's default, else absent.\n *\n * Every name the definition asks for is recorded, whether or not it was\n * supplied, so expansion can report a parameter the definition never declared.\n */\nfunction resolve(\n text: string,\n attrs: Map<string, string>,\n asked: Set<string>,\n): string | null {\n const match = PLACEHOLDER.exec(text.trim());\n if (!match) return text;\n const name = match[1]!;\n asked.add(name);\n return attrs.get(name) ?? match[2] ?? null;\n}\n\nfunction substitute(\n root: ParentNode,\n attrs: Map<string, string>,\n asked: Set<string>,\n): void {\n for (const scope of scopes(root)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n const value = resolve(attr.value, attrs, asked);\n // Presence propagates: a placeholder with nothing behind it drops the\n // attribute rather than emitting it empty, which is what HTML's own\n // boolean-attribute semantics require.\n if (value === null) el.removeAttribute(attr.name);\n else if (value !== attr.value) el.setAttribute(attr.name, value);\n }\n }\n\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n const texts: Text[] = [];\n while (walker.nextNode()) texts.push(walker.currentNode as Text);\n for (const text of texts) {\n const raw = text.data;\n if (!PLACEHOLDER.test(raw.trim())) continue;\n const value = resolve(raw, attrs, asked) ?? \"\";\n // Keep the author's surrounding whitespace: \"{{label}} <input>\" should\n // still separate the label from the control.\n const lead = raw.slice(0, raw.length - raw.trimStart().length);\n const tail = raw.slice(raw.trimEnd().length);\n text.data = lead + value + tail;\n }\n }\n}\n\n/**\n * Fill the definition's named destinations from the authored templates that\n * name them, and hand back the content that is left for the slot.\n *\n * A destination is filled with the template's contents, not with the\n * template, so what ships is ordinary markup in an ordinary place — a real\n * `<tr>` inside a real `<thead>`, visible in view-source like everything\n * else. The wrapper existed only to survive the parser and is gone.\n *\n * Unlike the slot, there may be several, because the reason for wrapping is\n * per-destination: a table has a head and a body, and each needs its own\n * markup past the same obstacle. They are matched by name, and an empty name\n * is a name, so a widget with one destination costs the author nothing.\n */\nfunction fillTemplates(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): globalThis.Node[] {\n const destinations = new Map<string, Element>();\n for (const el of root.querySelectorAll(`[${ATTR_EXP_TEMPLATE}]`)) {\n const name = el.getAttribute(ATTR_EXP_TEMPLATE)!;\n if (destinations.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares two ${ATTR_EXP_TEMPLATE}=\"${name}\" destinations`,\n );\n }\n destinations.set(name, el);\n el.removeAttribute(ATTR_EXP_TEMPLATE);\n }\n\n const rest: globalThis.Node[] = [];\n const filled = new Set<string>();\n for (const node of content) {\n const named =\n node instanceof HTMLTemplateElement &&\n node.hasAttribute(ATTR_EXP_TEMPLATE);\n if (!named) {\n rest.push(node);\n continue;\n }\n const template = node as HTMLTemplateElement;\n const name = template.getAttribute(ATTR_EXP_TEMPLATE)!;\n const destination = destinations.get(name);\n if (!destination) {\n throw new Error(\n `expand: a <template ${ATTR_EXP_TEMPLATE}=\"${name}\"> was written inside ` +\n `<${tag.toLowerCase()}>, whose definition has no destination by that name.`,\n );\n }\n if (filled.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given two templates for '${name}'`,\n );\n }\n filled.add(name);\n destination.append(template.content);\n }\n return rest;\n}\n\n/** Place authored inner content in the single slot, if the definition has one. */\nfunction fillSlot(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): void {\n const slots = root.querySelectorAll(`[${ATTR_EXP_SLOT}]`);\n if (slots.length > 1) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares more than one ${ATTR_EXP_SLOT}`,\n );\n }\n if (slots.length === 0) {\n // Whitespace is not content. An author who writes only named templates\n // still leaves the newlines between them, and those are the file's\n // formatting rather than something anybody meant to place.\n const wrote = content.some(\n (node) => node.nodeType !== Node.TEXT_NODE || (node as Text).data.trim(),\n );\n if (wrote) {\n throw new Error(\n `expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_EXP_SLOT}`,\n );\n }\n return;\n }\n const slot = slots[0]!;\n // The authored nodes move rather than copy. The caller replaces the tag's\n // children with the expanded tree immediately after, so there is nothing\n // left behind for a clone to protect.\n slot.append(...content);\n slot.removeAttribute(ATTR_EXP_SLOT);\n}\n\nfunction expandElement(el: Element, defs: Definitions, source: string): void {\n const tag = el.tagName.toUpperCase();\n\n // The tag survives expansion carrying every attribute the author wrote,\n // `exp-` ones included. They cost nothing where they are, and what ships\n // shows what was asked for beside what it produced.\n const attrs = new Map<string, string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(EXP_PREFIX)) continue;\n attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);\n }\n\n const tree = parseDefinition(source).cloneNode(true) as HTMLElement;\n const asked = new Set<string>();\n substitute(tree, attrs, asked);\n\n // A parameter the definition never declared is a name that resolves to\n // nothing. Unprefixed attributes could not support this check, because a\n // stray one is indistinguishable from a deliberate HTML attribute.\n for (const name of attrs.keys()) {\n if (asked.has(name)) continue;\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given ${EXP_PREFIX}${name}, ` +\n `but its definition has no {{${name}}}.`,\n );\n }\n // Named destinations first: what they take is addressed to them, and only\n // what is left is the slot's.\n fillSlot(tag, tree, fillTemplates(tag, tree, [...el.childNodes]));\n el.replaceChildren(...tree.childNodes);\n}\n\nfunction walk(root: ParentNode, defs: Definitions): void {\n for (const child of [...root.children]) {\n const source = defs[child.tagName.toUpperCase()];\n // A tag with no definition is left alone. It is a custom element a\n // script registers, and build/elements.ts is where a tag that has\n // neither a definition nor a script is refused — one rule, one place.\n if (source !== undefined) expandElement(child, defs, source);\n // Definitions may use other widgets, so descend after expanding. The\n // acyclicity check is what makes this terminate.\n walk(child, defs);\n // Template content is markup in a file like any other, and it is inert,\n // so a row template ships already expanded.\n if (child instanceof HTMLTemplateElement) walk(child.content, defs);\n }\n}\n\n/**\n * Expand every widget tag in an authored page. Throws rather than shipping.\n *\n * Definitions must come from `loadDefinitions`, which is where the graph is\n * checked for cycles. Handed a cyclic map directly, this recurses forever.\n */\nexport function expand(source: string, defs: Definitions): string {\n return expandTree(source, defs).innerHTML;\n}\n\n/**\n * The same, as the tree the browser builds from it: a template, whose content\n * the builder's later passes read and change before serializing it once.\n */\nexport function expandTree(\n source: string,\n defs: Definitions,\n): HTMLTemplateElement {\n const tree = parse(source);\n walk(tree, defs);\n return parsesBack(tree);\n}\n\n/**\n * The tree the browser builds from the expanded tree's text, provided it is\n * the same tree.\n *\n * Each file was parsed on its own, so everything inside one file already has\n * the shape the parser gives it. Expansion then moves one file's nodes into\n * another's through the DOM, which applies none of HTML's rules for what an\n * element may hold. Where two files meet, the tree can hold what no parse\n * would build: a definition's `<dialog>` inside a page's `<p>`. The browser\n * reads the text and closes the `<p>` early, and the dialog lands outside its\n * element with nothing logged.\n *\n * Reparsed in a template, the context a page ships in. Only a text that\n * differs is walked, to name what moved.\n */\nfunction parsesBack(tree: HTMLElement): HTMLTemplateElement {\n const html = tree.innerHTML;\n const reparsed = doc.createElement(\"template\");\n reparsed.innerHTML = html;\n if (reparsed.innerHTML === html) return reparsed;\n const moved = firstMoved(tree, reparsed.content) ?? tree;\n const path: string[] = [];\n let owner: string | null = null;\n for (let el: Node | null = moved; el && el !== tree; el = el.parentNode) {\n if (!(el instanceof Element)) continue;\n const tag = el.tagName.toLowerCase();\n path.unshift(tag);\n if (owner === null && el !== moved && CUSTOM_ELEMENT.test(tag)) {\n owner = tag;\n }\n }\n const what = path.length > 0 ? `<${path.at(-1)}>` : \"content\";\n const where = path.length > 0 ? path.join(\" > \") : \"the top level\";\n throw new Error(\n `expand: ${owner ? `<${owner}> puts` : \"expansion puts\"} ${what} ` +\n `where the browser's parser would not keep it, at ${where}. HTML does ` +\n `not let every element hold every other, and the browser moves what ` +\n `breaks the rule.`,\n );\n}\n\n/**\n * The first node, in document order, that the reparse does not have in the\n * same place. A node the reparse gained reports its parent.\n */\nfunction firstMoved(built: Node, parsed: Node): Node | null {\n const ours = built.childNodes;\n const theirs = parsed.childNodes;\n for (let i = 0; i < ours.length; i++) {\n const node = ours[i]!;\n const other = theirs[i];\n if (\n !other ||\n node.nodeName !== other.nodeName ||\n (node.nodeType !== Node.ELEMENT_NODE &&\n node.nodeValue !== other.nodeValue)\n ) {\n return node.nodeType === Node.ELEMENT_NODE ? node : built;\n }\n const inner =\n firstMoved(node, other) ??\n (node instanceof HTMLTemplateElement\n ? firstMoved(node.content, (other as HTMLTemplateElement).content)\n : null);\n if (inner) return inner;\n }\n return ours.length < theirs.length ? built : null;\n}\n"]}
|
|
@@ -3,6 +3,8 @@ export declare const ATTR_COLUMN = "lb-column";
|
|
|
3
3
|
export declare const ATTR_SHOW = "lb-show";
|
|
4
4
|
export declare const ATTR_COLUMN_VALUE = "lb-column-value";
|
|
5
5
|
export declare const ATTR_KEY_VALUE = "lb-key-value";
|
|
6
|
+
export declare const ATTR_ROW_LIVE = "lb-row-live";
|
|
7
|
+
export declare const LIVE_ROW = "[lb-row-live]";
|
|
6
8
|
export declare const ATTR_QUERY_ROW_COUNT = "lb-query-row-count";
|
|
7
9
|
export declare const ATTR_REQUEST = "lb-request";
|
|
8
10
|
export declare const LB_EVENT_NAME = "lb-request";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,WAAW,cAAc,CAAC;AAavC,eAAO,MAAM,SAAS,YAAY,CAAC;AAKnC,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAKnD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAK7C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,YAAY,eAAe,CAAC;AAIzC,eAAO,MAAM,aAAa,eAAe,CAAC;AAI1C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AAOrD,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAIxC,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,eAAO,MAAM,eAAe,8DAIlB,CAAC;AAGX,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,SAAS,SAAS,CAAC;AAKhC,eAAO,MAAM,SAAS,WAAW,CAAC;AAClC,eAAO,MAAM,eAAe,YAAY,CAAC;AACzC,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAGzD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAI3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAMjD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAGnD,eAAO,MAAM,SAAS,YAAY,CAAC;AAInC,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAK/C,eAAO,MAAM,oBAAoB,6GAQvB,CAAC;AAQX,eAAO,MAAM,YAAY,WAAW,CAAC;AASrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,WAAW,cAAc,CAAC;AAavC,eAAO,MAAM,SAAS,YAAY,CAAC;AAKnC,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAKnD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAM7C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAI3C,eAAO,MAAM,QAAQ,kBAAuB,CAAC;AAK7C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,YAAY,eAAe,CAAC;AAIzC,eAAO,MAAM,aAAa,eAAe,CAAC;AAI1C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AAOrD,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAIxC,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,eAAO,MAAM,eAAe,8DAIlB,CAAC;AAGX,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,SAAS,SAAS,CAAC;AAKhC,eAAO,MAAM,SAAS,WAAW,CAAC;AAClC,eAAO,MAAM,eAAe,YAAY,CAAC;AACzC,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAGzD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAI3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAMjD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAGnD,eAAO,MAAM,SAAS,YAAY,CAAC;AAInC,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAK/C,eAAO,MAAM,oBAAoB,6GAQvB,CAAC;AAQX,eAAO,MAAM,YAAY,WAAW,CAAC;AASrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
|
|
@@ -33,6 +33,14 @@ export const ATTR_COLUMN_VALUE = "lb-column-value";
|
|
|
33
33
|
// itself: the row's key. The key column is the query's, declared by the
|
|
34
34
|
// server and carried by every response item.
|
|
35
35
|
export const ATTR_KEY_VALUE = "lb-key-value";
|
|
36
|
+
// Stamped by the hub on each live row, and on nothing else: the element it
|
|
37
|
+
// cloned from a row template. A key alone does not say so, because an
|
|
38
|
+
// element a `row` lands on carries one too, and a live row may name a query
|
|
39
|
+
// of its own, so neither the key nor `lb-query` tells the two apart.
|
|
40
|
+
export const ATTR_ROW_LIVE = "lb-row-live";
|
|
41
|
+
// Selects a live row. Anything that walks rows — reconciliation, gathering,
|
|
42
|
+
// a custom element's own placement — finds them with this and nothing else.
|
|
43
|
+
export const LIVE_ROW = `[${ATTR_ROW_LIVE}]`;
|
|
36
44
|
// Stamped by the hub on an element with a row template: how many live rows
|
|
37
45
|
// it holds, so "no members yet" is a stylesheet rule rather than a
|
|
38
46
|
// conditional in every widget.
|