jops-core 1.0.15 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +356 -29
- package/bin/init.js +138 -20
- package/lib/jops-core.d.ts +251 -15
- package/lib/jops-core.min.js +1 -1
- package/lib/nextjs/JopsApp.d.ts +35 -0
- package/lib/nextjs/JopsApp.js +50 -0
- package/package.json +33 -5
- package/templates/nextjs-jops-spa/README.md +34 -0
- package/templates/nextjs-jops-spa/data/quotes.json +62 -0
- package/templates/nextjs-jops-spa/gitignore +21 -0
- package/templates/nextjs-jops-spa/next.config.ts +15 -0
- package/templates/nextjs-jops-spa/package.json +25 -0
- package/templates/nextjs-jops-spa/public/jops-spa/App.js +10 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Login.js +26 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Quotes.js +102 -0
- package/templates/nextjs-jops-spa/public/jops-spa/api.js +15 -0
- package/templates/nextjs-jops-spa/public/jops-spa/badge.js +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/format.js +5 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/login.html +34 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/main.html +17 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes.html +50 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes_row.html +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/skeleton.js +7 -0
- package/templates/nextjs-jops-spa/src/app/about/page.tsx +59 -0
- package/templates/nextjs-jops-spa/src/app/api/quotes/route.ts +11 -0
- package/templates/nextjs-jops-spa/src/app/globals.css +6 -0
- package/templates/nextjs-jops-spa/src/app/jops-spa/page.tsx +11 -0
- package/templates/nextjs-jops-spa/src/app/layout.tsx +27 -0
- package/templates/nextjs-jops-spa/src/app/page.tsx +5 -0
- package/templates/nextjs-jops-spa/tsconfig.json +21 -0
package/bin/init.js
CHANGED
|
@@ -3,14 +3,15 @@ import fs from "fs";
|
|
|
3
3
|
import https from "https";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import readline from "readline";
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
|
|
8
9
|
const [,, command, ...flags] = process.argv;
|
|
9
10
|
|
|
10
11
|
if (command !== "init" && command !== "build" && command !== "update") {
|
|
11
12
|
console.log("Usage:");
|
|
12
|
-
console.log(" npx jops-core init — vendor library and configure index.html");
|
|
13
|
-
console.log(" npx jops-core update — copy the latest jops-core.min.js to lib/");
|
|
13
|
+
console.log(" npx jops-core init — vendor library and configure index.html, or create a template project");
|
|
14
|
+
console.log(" npx jops-core update — copy the latest jops-core.min.js to lib/ (public/lib/ in a Next.js project)");
|
|
14
15
|
console.log(" npx jops-core build — generate production-ready dist/ folder");
|
|
15
16
|
console.log("");
|
|
16
17
|
console.log("Options (init, update):");
|
|
@@ -143,6 +144,133 @@ function copyDir(src, dest) {
|
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
// A Next.js project: the library is served from public/lib/.
|
|
148
|
+
function isNextProject(dir) {
|
|
149
|
+
return ["next.config.ts", "next.config.js", "next.config.mjs"].some((f) => fs.existsSync(path.join(dir, f)));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Copies a template project into dest (README.md included, unlike copyDir()).
|
|
153
|
+
// package.json is merged separately; existing files are never overwritten.
|
|
154
|
+
// npm drops .gitignore files from published packages, so the template stores
|
|
155
|
+
// it as "gitignore".
|
|
156
|
+
function copyTemplateProject(src, dest, root = dest) {
|
|
157
|
+
const SKIP = new Set(["node_modules", ".next", ".DS_Store", "package.json"]);
|
|
158
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
159
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
160
|
+
if (dest === root && SKIP.has(entry.name)) continue;
|
|
161
|
+
if (entry.name === ".DS_Store") continue;
|
|
162
|
+
const srcPath = path.join(src, entry.name);
|
|
163
|
+
const destPath = path.join(dest, entry.name === "gitignore" ? ".gitignore" : entry.name);
|
|
164
|
+
if (entry.isDirectory()) copyTemplateProject(srcPath, destPath, root);
|
|
165
|
+
else if (fs.existsSync(destPath)) console.log(`[jops-core] Kept the existing ${path.relative(root, destPath)}`);
|
|
166
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Creates the template's package.json, or merges its dependencies and scripts
|
|
171
|
+
// into an existing one: existing entries (e.g. an installed jops-core) are kept.
|
|
172
|
+
function mergeTemplatePackageJson(templatePath, targetPath) {
|
|
173
|
+
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
174
|
+
if (!fs.existsSync(targetPath)) {
|
|
175
|
+
fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n", "utf8");
|
|
176
|
+
console.log("[jops-core] Created package.json");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const pkg = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
|
180
|
+
pkg.private ??= true;
|
|
181
|
+
// `npm init -y` writes "type": "commonjs", which makes Next.js (Turbopack)
|
|
182
|
+
// reject the project's ES module sources; Next.js projects have no "type".
|
|
183
|
+
if (pkg.type === "commonjs") {
|
|
184
|
+
delete pkg.type;
|
|
185
|
+
console.log('[jops-core] Removed "type": "commonjs" from package.json (Next.js uses ES modules)');
|
|
186
|
+
}
|
|
187
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
188
|
+
for (const [name, spec] of Object.entries(template[section] ?? {})) {
|
|
189
|
+
const existing = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
190
|
+
if (existing) {
|
|
191
|
+
if (name === "jops-core") console.log(`[jops-core] Kept the installed jops-core (${existing})`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
pkg[section] = { ...pkg[section], [name]: spec };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [name, command] of Object.entries(template.scripts ?? {})) {
|
|
198
|
+
const existing = pkg.scripts?.[name];
|
|
199
|
+
if (existing && existing !== command) {
|
|
200
|
+
console.log(`[jops-core] Kept the existing "${name}" script (${existing}); the template uses "${command}"`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
pkg.scripts = { ...pkg.scripts, [name]: command };
|
|
204
|
+
}
|
|
205
|
+
fs.writeFileSync(targetPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
206
|
+
console.log("[jops-core] Added the Next.js dependencies and scripts to package.json");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── template selection ──────────────────────────────────────────────────────
|
|
210
|
+
// Asked before init writes anything, because the Next.js template creates its
|
|
211
|
+
// own project folder instead of configuring the current one.
|
|
212
|
+
|
|
213
|
+
async function askTemplate() {
|
|
214
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
215
|
+
const answer = await new Promise((resolve) => rl.question(
|
|
216
|
+
"\nCreate JOPS project from existing templates:\n" +
|
|
217
|
+
" 1. Empty JOPS project (basic skeleton for JOPS project architecture)\n" +
|
|
218
|
+
" 2. Hello World JOPS project (an example project utilizing all features)\n" +
|
|
219
|
+
" 3. Next.js JOPS Embedded SPA (a Next.js project hosting a JOPS SPA)\n" +
|
|
220
|
+
" 4. None (I'll create my own architecture)\n" +
|
|
221
|
+
"Enter choice (1-4): ",
|
|
222
|
+
resolve,
|
|
223
|
+
));
|
|
224
|
+
rl.close();
|
|
225
|
+
return answer.trim();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const choice = command === "init" ? await askTemplate() : null;
|
|
229
|
+
|
|
230
|
+
// ─── init: Next.js JOPS Embedded SPA (template 3) ────────────────────────────
|
|
231
|
+
|
|
232
|
+
if (command === "init" && choice === "3") {
|
|
233
|
+
const cwd = process.cwd();
|
|
234
|
+
const templateDir = path.join(__dirname, "../templates/nextjs-jops-spa");
|
|
235
|
+
// Paths that would clash with an existing project: nothing is written then.
|
|
236
|
+
const conflicts = [
|
|
237
|
+
"next.config.ts", "next.config.js", "next.config.mjs",
|
|
238
|
+
"tsconfig.json", "src/app", "public/jops-spa", "data/quotes.json",
|
|
239
|
+
].filter((p) => fs.existsSync(path.join(cwd, p)));
|
|
240
|
+
|
|
241
|
+
if (isHybrid) {
|
|
242
|
+
console.error(`[jops-core] This is a ${type} project; create the Next.js project in another folder.`);
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
} else if (conflicts.length) {
|
|
245
|
+
console.error(`[jops-core] This folder already has ${conflicts.join(", ")}. Nothing was changed.`);
|
|
246
|
+
console.error(" Run init in an empty folder (or a fresh npm project) to create the Next.js project.");
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
} else {
|
|
249
|
+
copyTemplateProject(templateDir, cwd);
|
|
250
|
+
mergeTemplatePackageJson(path.join(templateDir, "package.json"), path.join(cwd, "package.json"));
|
|
251
|
+
console.log("[jops-core] Created the Next.js JOPS Embedded SPA project in this folder.");
|
|
252
|
+
|
|
253
|
+
const libDir = path.join(cwd, "public", "lib");
|
|
254
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
255
|
+
fs.copyFileSync(path.join(__dirname, "../lib/jops-core.min.js"), path.join(libDir, "jops-core.min.js"));
|
|
256
|
+
console.log("[jops-core] Vendored jops-core.min.js → public/lib/jops-core.min.js");
|
|
257
|
+
|
|
258
|
+
console.log("[jops-core] Installing dependencies (npm install)…");
|
|
259
|
+
const install = spawnSync("npm", ["install"], {
|
|
260
|
+
cwd,
|
|
261
|
+
stdio: "inherit",
|
|
262
|
+
shell: process.platform === "win32",
|
|
263
|
+
});
|
|
264
|
+
if (install.status !== 0) {
|
|
265
|
+
console.warn("[jops-core] npm install did not complete. Run it yourself: npm install");
|
|
266
|
+
}
|
|
267
|
+
console.log("");
|
|
268
|
+
console.log("[jops-core] Next steps:");
|
|
269
|
+
console.log(" npm run dev");
|
|
270
|
+
console.log(" open http://localhost:3000/about and http://localhost:3000/jops-spa");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
146
274
|
// ─── index.html helpers (hybrid projects) ────────────────────────────────────
|
|
147
275
|
|
|
148
276
|
// Same policy as Cordova's default, plus 'unsafe-inline' for the inline import map
|
|
@@ -242,7 +370,7 @@ function schemeWarnings(xml) {
|
|
|
242
370
|
|
|
243
371
|
// ─── init ────────────────────────────────────────────────────────────────────
|
|
244
372
|
|
|
245
|
-
if (command === "init") {
|
|
373
|
+
if (command === "init" && choice !== "3") {
|
|
246
374
|
fs.mkdirSync(webRoot, { recursive: true });
|
|
247
375
|
if (isHybrid) {
|
|
248
376
|
console.log(`[jops-core] ${forced ? "Using" : "Detected"} ${type} project → web folder: ${rel(webRoot)}/`);
|
|
@@ -405,22 +533,10 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
405
533
|
if (changed) fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
406
534
|
}
|
|
407
535
|
|
|
408
|
-
// Template
|
|
409
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
410
|
-
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
411
|
-
|
|
412
|
-
const choice = await ask(
|
|
413
|
-
"\nCreate JOPS project from existing templates:\n" +
|
|
414
|
-
" 1. Empty JOPS project (basic skeleton for JOPS project architecture)\n" +
|
|
415
|
-
" 2. Hello World JOPS project (an example project utilizing all features)\n" +
|
|
416
|
-
" 3. None (I'll create my own architecture)\n" +
|
|
417
|
-
"Enter choice (1-3): "
|
|
418
|
-
);
|
|
419
|
-
rl.close();
|
|
420
|
-
|
|
536
|
+
// Template choice: asked at the start (see askTemplate()).
|
|
421
537
|
// Cordova's sample page (css/index.css, js/index.js, #deviceready) conflicts with a JOPS
|
|
422
538
|
// template: back it up and start from the JOPS Cordova index.html instead.
|
|
423
|
-
if (type === "cordova" && ["1", "2"].includes(choice
|
|
539
|
+
if (type === "cordova" && ["1", "2"].includes(choice) &&
|
|
424
540
|
fs.readFileSync(indexPath, "utf8").includes('id="deviceready"')) {
|
|
425
541
|
let backup = path.join(webRoot, "index.cordova.html");
|
|
426
542
|
for (let n = 1; fs.existsSync(backup); n++) backup = path.join(webRoot, `index.cordova-${n}.html`);
|
|
@@ -430,7 +546,7 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
430
546
|
}
|
|
431
547
|
|
|
432
548
|
const templateSrc = path.join(__dirname, "../templates");
|
|
433
|
-
if (choice
|
|
549
|
+
if (choice === "1") {
|
|
434
550
|
copyDir(path.join(templateSrc, "empty"), webRoot);
|
|
435
551
|
// Add data-app to the module script in index.html
|
|
436
552
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
@@ -440,7 +556,7 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
440
556
|
);
|
|
441
557
|
fs.writeFileSync(indexPath, html, "utf8");
|
|
442
558
|
console.log("[jops-core] Created empty JOPS project skeleton.");
|
|
443
|
-
} else if (choice
|
|
559
|
+
} else if (choice === "2") {
|
|
444
560
|
copyDir(path.join(templateSrc, "hello-world"), webRoot);
|
|
445
561
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
446
562
|
html = html.replace(
|
|
@@ -457,7 +573,9 @@ ${cordovaBody}${MODULE_SCRIPT}
|
|
|
457
573
|
// ─── update ──────────────────────────────────────────────────────────────────
|
|
458
574
|
|
|
459
575
|
if (command === "update") {
|
|
460
|
-
|
|
576
|
+
// A Next.js project serves the library from public/lib/.
|
|
577
|
+
const libRoot = type === "web" && isNextProject(projectRoot) ? path.join(projectRoot, "public") : webRoot;
|
|
578
|
+
const libDir = path.join(libRoot, "lib");
|
|
461
579
|
const vendorDest = path.join(libDir, "jops-core.min.js");
|
|
462
580
|
const vendorSrc = path.join(__dirname, "../lib/jops-core.min.js");
|
|
463
581
|
|
package/lib/jops-core.d.ts
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts a JOPS app inside a given element instead of `<body>`. Used by hosts
|
|
3
|
+
* that control when and where the app runs, such as `<JopsApp>` in Next.js
|
|
4
|
+
* (jops-core loaded with `data-host`, which exposes `window.JOPS.mount`).
|
|
5
|
+
* @param {{ app: string, root: HTMLElement }} options - `app`: URL of the app's
|
|
6
|
+
* entry module (default export is the App class); `root`: element the app's
|
|
7
|
+
* root view renders into.
|
|
8
|
+
* @returns {Promise<object>} The App instance.
|
|
9
|
+
* @example
|
|
10
|
+
* await mount({ app: "/quotes/App.js", root: document.getElementById("quotes") });
|
|
11
|
+
*/
|
|
12
|
+
export declare function mount({ app, root }: {
|
|
13
|
+
app: string;
|
|
14
|
+
root: HTMLElement;
|
|
15
|
+
}): Promise<object>;
|
|
16
|
+
/**
|
|
17
|
+
* Ends the hosted app completely, like closing a standalone app: destroys the
|
|
18
|
+
* Router (listeners, singleton, routes, stacked views) and every view, empties
|
|
19
|
+
* the host root, clears the Store and the EventBus, and restores the host's
|
|
20
|
+
* history methods. The current URL and history entry are left alone: they
|
|
21
|
+
* belong to the host page. Safe to call more than once.
|
|
22
|
+
*
|
|
23
|
+
* Called by the Router itself when history leaves the app's page (Back past
|
|
24
|
+
* the entry the app was launched from, or a host link to another page), and
|
|
25
|
+
* by hosts such as `<JopsApp>` when they remove the app.
|
|
26
|
+
* @example
|
|
27
|
+
* window.JOPS.unmount();
|
|
28
|
+
*/
|
|
29
|
+
export declare function unmount(): void;
|
|
1
30
|
declare class RawHTML {
|
|
2
31
|
html: any;
|
|
3
32
|
constructor(html: any);
|
|
@@ -26,13 +55,43 @@ export declare function layout(strings: TemplateStringsArray, ...values: any[]):
|
|
|
26
55
|
* Wraps a single child element for reading and writing.
|
|
27
56
|
*/
|
|
28
57
|
declare class Element {
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
#private;
|
|
59
|
+
id: string;
|
|
60
|
+
inert: boolean;
|
|
61
|
+
element: globalThis.Element;
|
|
31
62
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param {string}
|
|
63
|
+
* @param {HTMLElement[]} roots - The view's root elements, searched in order.
|
|
64
|
+
* @param {string} id - The id to look up; the first root that has it wins.
|
|
34
65
|
*/
|
|
35
|
-
|
|
66
|
+
constructor(roots: HTMLElement[], id: string);
|
|
67
|
+
/**
|
|
68
|
+
* Wraps an element that was already found (used by `View.find(id)`).
|
|
69
|
+
* @param {HTMLElement} element
|
|
70
|
+
* @param {string} id
|
|
71
|
+
* @returns {Element}
|
|
72
|
+
*/
|
|
73
|
+
static from(element: HTMLElement, id: string): Element;
|
|
74
|
+
/**
|
|
75
|
+
* Sets the `innerHTML` of the element. The value is not escaped: pass values
|
|
76
|
+
* from users or the network through the `layout` tag.
|
|
77
|
+
* @param {string} html - Plain text or HTML string.
|
|
78
|
+
* @returns {Element} This wrapper, for chaining.
|
|
79
|
+
* @example
|
|
80
|
+
* this.get("title").set(layout`${quote.number}`);
|
|
81
|
+
*/
|
|
82
|
+
set(html: string): Element;
|
|
83
|
+
/**
|
|
84
|
+
* Sets an attribute, or removes it when `value` is `null` or `undefined`.
|
|
85
|
+
* An empty string sets a boolean attribute (`disabled`, `hidden`, `required`).
|
|
86
|
+
* @param {string} name - Attribute name, e.g. "href".
|
|
87
|
+
* @param {string|number|boolean|null} [value] - Attribute value.
|
|
88
|
+
* @returns {Element} This wrapper, for chaining.
|
|
89
|
+
* @example
|
|
90
|
+
* this.get("quote_id").set(layout`${q.number}`).attr("href", `#quote?id=${q.id}`);
|
|
91
|
+
* this.get("save").attr("disabled", ""); // set
|
|
92
|
+
* this.get("save").attr("disabled", null); // remove
|
|
93
|
+
*/
|
|
94
|
+
attr(name: string, value?: string | number | boolean | null): Element;
|
|
36
95
|
/**
|
|
37
96
|
* Returns the current `innerHTML` of the element.
|
|
38
97
|
* @returns {string}
|
|
@@ -46,9 +105,16 @@ declare class Element {
|
|
|
46
105
|
* 1. {@link View#loadLayout} — load or assign `this.layout`
|
|
47
106
|
* 2. {@link View#inflate} — parse `this.layout` into `this.domNode`
|
|
48
107
|
* 3. {@link View#onLayout} — DOM is live; add subviews, read initial data
|
|
49
|
-
* 4. {@link View#onResume} — view becomes visible; called again on every return navigation
|
|
108
|
+
* 4. {@link View#onResume} — view becomes visible; called again on every return navigation,
|
|
109
|
+
* and when the same route is shown again with new parameters
|
|
50
110
|
* 5. {@link View#bindEvents} — wire `on*` attributes to class methods
|
|
51
111
|
*
|
|
112
|
+
* Optional hooks, also called by the framework:
|
|
113
|
+
* - `onPause()` — a different view comes to the front and this one stays alive
|
|
114
|
+
* (hidden route, or covered by a stacked view). Not called when the view is destroyed
|
|
115
|
+
* (Back from a stacked view, a stack closed by a flat link, unmount()).
|
|
116
|
+
* - `onBackPressed()` — Back navigation from this view (called before onPause() for routes).
|
|
117
|
+
*
|
|
52
118
|
* @example
|
|
53
119
|
* import { View, layout } from "jops-core";
|
|
54
120
|
*
|
|
@@ -63,17 +129,35 @@ declare class Element {
|
|
|
63
129
|
*/
|
|
64
130
|
export declare class View {
|
|
65
131
|
#private;
|
|
132
|
+
_generation: number;
|
|
66
133
|
subViews: any[];
|
|
67
134
|
layout: any;
|
|
68
135
|
style: string;
|
|
136
|
+
domNodes: any[];
|
|
69
137
|
domNode: any;
|
|
138
|
+
_destroyed: boolean;
|
|
139
|
+
_borrowedDomNode: boolean;
|
|
70
140
|
/**
|
|
71
141
|
* @param {string} [layout] - Optional layout path or inline HTML string.
|
|
142
|
+
* @param {Record<string, string>} [props] - Input properties. A view declared
|
|
143
|
+
* in a layout (`<Card jops src="…" title="Hi">`) gets them from the tag's
|
|
144
|
+
* attributes automatically; a view created in code may pass them here.
|
|
72
145
|
*/
|
|
73
|
-
constructor(layout?: string);
|
|
146
|
+
constructor(layout?: string, props?: Record<string, string>);
|
|
147
|
+
/**
|
|
148
|
+
* The view's input properties (read-only): the attributes of its declared
|
|
149
|
+
* jops tag (except `jops`, `type="jops"`, `src`, `layout`, `path`,
|
|
150
|
+
* `jops-event-bind`), names in camelCase, values as strings; or the props
|
|
151
|
+
* passed to the constructor. `{}` when there are none.
|
|
152
|
+
* @returns {Readonly<Record<string, string>>}
|
|
153
|
+
*/
|
|
154
|
+
get props(): Readonly<Record<string, string>>;
|
|
74
155
|
/**
|
|
75
156
|
* Override to load a layout file or set `this.layout` inline.
|
|
76
157
|
* Called automatically by the framework before `inflate()`.
|
|
158
|
+
* Loads once per view: overlapping calls (with or without a path) wait for
|
|
159
|
+
* the load in progress, and once a layout is set later calls return at once.
|
|
160
|
+
* A failed load leaves the view unloaded, so it can be retried.
|
|
77
161
|
* @param {string} [layout] - Path to a `.js` layout module, a `.html` file, or an inline HTML string.
|
|
78
162
|
* `.js` — dynamically imported as an ES module; must export a `layout\`...\`` string as default.
|
|
79
163
|
* `.html` — fetched and cached for the page lifetime; plain HTML fragment, no import/export needed.
|
|
@@ -83,6 +167,7 @@ export declare class View {
|
|
|
83
167
|
loadLayout(layout?: string): Promise<void>;
|
|
84
168
|
/**
|
|
85
169
|
* Injects a `.css` file link or an inline `<style>` tag into `<head>`, deduped.
|
|
170
|
+
* No effect in hosted mode (e.g. Next.js through `<JopsApp>`): the host owns the CSS.
|
|
86
171
|
* @param {string} style - Path to a `.css` file, or a raw CSS string.
|
|
87
172
|
*/
|
|
88
173
|
setStyle(style: string): void;
|
|
@@ -94,6 +179,19 @@ export declare class View {
|
|
|
94
179
|
* this.get("username").set("Alice");
|
|
95
180
|
*/
|
|
96
181
|
get(id: string): Element;
|
|
182
|
+
/**
|
|
183
|
+
* The view's root elements: every top-level element of its layout (a layout
|
|
184
|
+
* may have more than one), or the borrowed container of a root view.
|
|
185
|
+
* @returns {HTMLElement[]}
|
|
186
|
+
* @protected
|
|
187
|
+
*/
|
|
188
|
+
protected _roots(): HTMLElement[];
|
|
189
|
+
/**
|
|
190
|
+
* Hides or shows every root element (used by the Router).
|
|
191
|
+
* @param {boolean} hidden
|
|
192
|
+
* @protected
|
|
193
|
+
*/
|
|
194
|
+
protected _setHidden(hidden: boolean): void;
|
|
97
195
|
/**
|
|
98
196
|
* Wires `on*` HTML attributes on `[jops-event-bind]` elements to
|
|
99
197
|
* same-named methods on this view instance.
|
|
@@ -108,6 +206,7 @@ export declare class View {
|
|
|
108
206
|
/**
|
|
109
207
|
* Appends `view` into the child element with `id="parentNode"` and runs its
|
|
110
208
|
* full lifecycle (`onLayout` → `onResume` → `bindEvents`).
|
|
209
|
+
* A removed (destroyed) view is final: create a new instance to show it again.
|
|
111
210
|
* @param {string} parentNode - The `id` of the container element inside `this.domNode`.
|
|
112
211
|
* @param {View} view - The subview to mount.
|
|
113
212
|
* @returns {Promise<void>}
|
|
@@ -121,14 +220,50 @@ export declare class View {
|
|
|
121
220
|
* Removes the inline `display` override, restoring `this.domNode` visibility.
|
|
122
221
|
*/
|
|
123
222
|
show(): void;
|
|
223
|
+
/**
|
|
224
|
+
* Every element with the given id across all root elements of the layout
|
|
225
|
+
* (and the DOM of its sub-views), in page order.
|
|
226
|
+
* @overload
|
|
227
|
+
* @param {string} id - Element id.
|
|
228
|
+
* @returns {Element[]}
|
|
229
|
+
* @example
|
|
230
|
+
* this.find("quote_id").forEach((el) => el.attr("tabindex", 0));
|
|
231
|
+
*/
|
|
124
232
|
/**
|
|
125
233
|
* Depth-first search across the full subview subtree.
|
|
126
|
-
* @
|
|
234
|
+
* @overload
|
|
235
|
+
* @param {(view: View) => boolean} condition - Predicate to match against each view.
|
|
127
236
|
* @returns {View[]} Flat array of all matching views.
|
|
128
237
|
* @example
|
|
129
238
|
* const forms = root.find(v => v instanceof FormView);
|
|
130
239
|
*/
|
|
131
|
-
|
|
240
|
+
/**
|
|
241
|
+
* @param {string | ((view: View) => boolean)} arg
|
|
242
|
+
* @returns {Element[] | View[]}
|
|
243
|
+
*/
|
|
244
|
+
find(id: string): Element[];
|
|
245
|
+
/**
|
|
246
|
+
* Every element with the given id across all root elements of the layout
|
|
247
|
+
* (and the DOM of its sub-views), in page order.
|
|
248
|
+
* @overload
|
|
249
|
+
* @param {string} id - Element id.
|
|
250
|
+
* @returns {Element[]}
|
|
251
|
+
* @example
|
|
252
|
+
* this.find("quote_id").forEach((el) => el.attr("tabindex", 0));
|
|
253
|
+
*/
|
|
254
|
+
/**
|
|
255
|
+
* Depth-first search across the full subview subtree.
|
|
256
|
+
* @overload
|
|
257
|
+
* @param {(view: View) => boolean} condition - Predicate to match against each view.
|
|
258
|
+
* @returns {View[]} Flat array of all matching views.
|
|
259
|
+
* @example
|
|
260
|
+
* const forms = root.find(v => v instanceof FormView);
|
|
261
|
+
*/
|
|
262
|
+
/**
|
|
263
|
+
* @param {string | ((view: View) => boolean)} arg
|
|
264
|
+
* @returns {Element[] | View[]}
|
|
265
|
+
*/
|
|
266
|
+
find(condition: (view: View) => boolean): View[];
|
|
132
267
|
/**
|
|
133
268
|
* Recursively destroys the subview subtree: calls `destroy()` on all
|
|
134
269
|
* subviews, removes `this.domNode` from the DOM, and nulls references.
|
|
@@ -141,11 +276,21 @@ export declare class View {
|
|
|
141
276
|
removeSubView(view: View): void;
|
|
142
277
|
/**
|
|
143
278
|
* Inserts `view` at `index` within `this.domNode` and runs its full lifecycle.
|
|
279
|
+
* A removed (destroyed) view is final: create a new instance to show it again.
|
|
144
280
|
* @param {View} view - The subview to insert.
|
|
145
281
|
* @param {number} index - Zero-based insertion position.
|
|
146
282
|
* @returns {Promise<void>}
|
|
147
283
|
*/
|
|
148
284
|
insertSubView(view: View, index: number): Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Final lifecycle steps for a sub-view declared in this view's layout,
|
|
287
|
+
* called by `render()` after its `onLayout()`. Router overrides it so a
|
|
288
|
+
* route is resumed only when it is shown.
|
|
289
|
+
* @param {View} subView
|
|
290
|
+
* @returns {Promise<void>}
|
|
291
|
+
* @protected
|
|
292
|
+
*/
|
|
293
|
+
protected _startSubView(subView: View): Promise<void>;
|
|
149
294
|
/**
|
|
150
295
|
* Mounts the view to the DOM, resolves all nested `[jops]` view
|
|
151
296
|
* elements and `<Router>` declarations, and runs each one's full lifecycle.
|
|
@@ -159,30 +304,83 @@ export declare class View {
|
|
|
159
304
|
* Static pub/sub event bus for cross-view communication.
|
|
160
305
|
* All methods are static — no instance needed.
|
|
161
306
|
*
|
|
307
|
+
* A subscription with an owner (a view) is removed automatically when the
|
|
308
|
+
* owner is destroyed; a global subscription (no owner) stays until it is
|
|
309
|
+
* unsubscribed (the developer's responsibility).
|
|
310
|
+
*
|
|
162
311
|
* @example
|
|
163
|
-
* EventBus.subscribe("
|
|
312
|
+
* EventBus.subscribe(this, "quotes:changed", (q) => this.refresh(q)); // owned
|
|
313
|
+
* EventBus.subscribe("user:login", onLogin); // global
|
|
164
314
|
* EventBus.send("user:login", { name: "Alice" });
|
|
165
315
|
*/
|
|
166
316
|
export declare class EventBus {
|
|
167
317
|
#private;
|
|
168
318
|
/**
|
|
169
|
-
* Registers a listener
|
|
319
|
+
* Registers a global listener; unsubscribe it with `unsubscribe()`.
|
|
320
|
+
* @overload
|
|
321
|
+
* @param {string} event - Event name.
|
|
322
|
+
* @param {(data: any) => void} callback - Handler called with the event payload.
|
|
323
|
+
* @returns {void}
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* Registers a listener owned by `owner` (usually a view): it is removed
|
|
327
|
+
* automatically when the owner is destroyed.
|
|
328
|
+
* @overload
|
|
329
|
+
* @param {object} owner - The object owning the subscription, e.g. `this`.
|
|
330
|
+
* @param {string} event - Event name.
|
|
331
|
+
* @param {(data: any) => void} callback - Handler called with the event payload.
|
|
332
|
+
* @returns {void}
|
|
333
|
+
*/
|
|
334
|
+
/**
|
|
335
|
+
* @param {...*} args - `(event, callback)` or `(owner, event, callback)`.
|
|
336
|
+
* @returns {void}
|
|
337
|
+
*/
|
|
338
|
+
static subscribe(event: string, callback: (data: any) => void): void;
|
|
339
|
+
/**
|
|
340
|
+
* Registers a global listener; unsubscribe it with `unsubscribe()`.
|
|
341
|
+
* @overload
|
|
342
|
+
* @param {string} event - Event name.
|
|
343
|
+
* @param {(data: any) => void} callback - Handler called with the event payload.
|
|
344
|
+
* @returns {void}
|
|
345
|
+
*/
|
|
346
|
+
/**
|
|
347
|
+
* Registers a listener owned by `owner` (usually a view): it is removed
|
|
348
|
+
* automatically when the owner is destroyed.
|
|
349
|
+
* @overload
|
|
350
|
+
* @param {object} owner - The object owning the subscription, e.g. `this`.
|
|
170
351
|
* @param {string} event - Event name.
|
|
171
|
-
* @param {
|
|
352
|
+
* @param {(data: any) => void} callback - Handler called with the event payload.
|
|
353
|
+
* @returns {void}
|
|
172
354
|
*/
|
|
173
|
-
static subscribe(event: string, callback: Function): void;
|
|
174
355
|
/**
|
|
175
|
-
*
|
|
356
|
+
* @param {...*} args - `(event, callback)` or `(owner, event, callback)`.
|
|
357
|
+
* @returns {void}
|
|
358
|
+
*/
|
|
359
|
+
static subscribe(owner: object, event: string, callback: (data: any) => void): void;
|
|
360
|
+
/**
|
|
361
|
+
* Fires the named event to all subscribers. Subscriptions of destroyed
|
|
362
|
+
* owners are skipped and removed (a safety net: `destroy()` normally
|
|
363
|
+
* removes them first).
|
|
176
364
|
* @param {string} event - Event name.
|
|
177
365
|
* @param {*} [data] - Optional payload passed to every subscriber.
|
|
178
366
|
*/
|
|
179
367
|
static send(event: string, data?: any): void;
|
|
180
368
|
/**
|
|
181
|
-
* Removes a specific listener by reference.
|
|
369
|
+
* Removes a specific listener by reference (global or owned).
|
|
182
370
|
* @param {string} event - Event name.
|
|
183
371
|
* @param {function(*): void} callback - The exact function reference passed to `subscribe`.
|
|
184
372
|
*/
|
|
185
373
|
static unsubscribe(event: string, callback: Function): void;
|
|
374
|
+
/**
|
|
375
|
+
* Removes every subscription owned by `owner`, across all events (called by
|
|
376
|
+
* `View.destroy()`). Global subscriptions are not touched.
|
|
377
|
+
* @param {object} owner - The owner passed to `subscribe(owner, event, callback)`.
|
|
378
|
+
*/
|
|
379
|
+
static unsubscribeAll(owner: object): void;
|
|
380
|
+
/**
|
|
381
|
+
* Removes every listener of every event (called by `unmount()`).
|
|
382
|
+
*/
|
|
383
|
+
static clear(): void;
|
|
186
384
|
}
|
|
187
385
|
/**
|
|
188
386
|
* App-wide runtime state container. Singleton. No reactivity.
|
|
@@ -244,7 +442,10 @@ export declare class Router extends View {
|
|
|
244
442
|
routes: {};
|
|
245
443
|
_navStack: any[];
|
|
246
444
|
_baseView: any;
|
|
445
|
+
_defaultPath: string;
|
|
446
|
+
_pathname: string;
|
|
247
447
|
_idx: any;
|
|
448
|
+
_historyListener: () => void;
|
|
248
449
|
_currentView: any;
|
|
249
450
|
/**
|
|
250
451
|
* Returns the single Router instance after it has been rendered.
|
|
@@ -253,6 +454,15 @@ export declare class Router extends View {
|
|
|
253
454
|
static singleton(): Router;
|
|
254
455
|
constructor(routerDomNode: any);
|
|
255
456
|
onLayout(): Promise<void>;
|
|
457
|
+
/**
|
|
458
|
+
* Routes only get their events bound while the Router renders; `onResume()`
|
|
459
|
+
* runs when a route is shown (`_switchTo()` and the transitions), so hidden
|
|
460
|
+
* routes are not resumed at startup.
|
|
461
|
+
* @param {View} subView
|
|
462
|
+
* @returns {Promise<void>}
|
|
463
|
+
* @protected
|
|
464
|
+
*/
|
|
465
|
+
protected _startSubView(subView: View): Promise<void>;
|
|
256
466
|
initRouter(): void;
|
|
257
467
|
/**
|
|
258
468
|
* Single entry point for popstate and hashchange. Decides the direction
|
|
@@ -261,7 +471,33 @@ export declare class Router extends View {
|
|
|
261
471
|
_onHistoryChange(): void;
|
|
262
472
|
_onBack(): void;
|
|
263
473
|
_onForward(): void;
|
|
474
|
+
/** Destroys every stacked navigate() view (switching to a flat route). */
|
|
475
|
+
_closeStack(): void;
|
|
476
|
+
/**
|
|
477
|
+
* onPause() on the view leaving the front: a different view is coming to
|
|
478
|
+
* the front and the current one stays alive (hidden or covered). Not used
|
|
479
|
+
* when a view is destroyed (stack pop on Back, _closeStack(), unmount()).
|
|
480
|
+
* @param {View} incoming
|
|
481
|
+
*/
|
|
482
|
+
_pauseCurrent(incoming: View): void;
|
|
264
483
|
_switchToHash(): void;
|
|
484
|
+
/**
|
|
485
|
+
* Replaces the current entry's empty hash with the default (first) route,
|
|
486
|
+
* keeping the existing history state, and shows that route.
|
|
487
|
+
*/
|
|
488
|
+
_switchToDefault(): void;
|
|
489
|
+
/**
|
|
490
|
+
* Removes the history listeners, destroys the routes and stacked views and
|
|
491
|
+
* clears the singleton. Called by `unmount()`; safe to call more than once.
|
|
492
|
+
*/
|
|
493
|
+
destroy(): void;
|
|
494
|
+
/** pushState/replaceState marked as JOPS's own (see installHostHistoryHook). */
|
|
495
|
+
_history(method: any, state: any, url: any): void;
|
|
496
|
+
/**
|
|
497
|
+
* Hosted mode: called after the host changed the URL with pushState or
|
|
498
|
+
* replaceState (no browser event fires for those).
|
|
499
|
+
*/
|
|
500
|
+
_onHostNavigation(): void;
|
|
265
501
|
_animateTransition(outgoing: any, incoming: any, direction: any): Promise<void>;
|
|
266
502
|
_slideTransition(outgoing: any, incoming: any, direction: any): Promise<void>;
|
|
267
503
|
_fadeTransition(outgoing: any, incoming: any): Promise<void>;
|