@nutin/cli 2.1.1 → 3.0.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/CHANGELOG.md +268 -0
- package/README.md +13 -5
- package/bin/add-feature.mjs +1 -1
- package/bin/cli.mjs +1 -1
- package/bin/create-app.mjs +1 -1
- package/bin/update-app.mjs +1 -1
- package/lib/index.mjs +6 -2
- package/lib/src/add-feature/base-template-patcher.mjs +139 -0
- package/lib/src/add-feature/feature-adder.mjs +20 -9
- package/lib/src/add-feature/feature-context-builder.mjs +1 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +34 -8
- package/lib/src/add-feature/package-json-updater.mjs +7 -9
- package/lib/src/common/feature-registry.mjs +4 -0
- package/lib/src/common/file-generator.mjs +25 -3
- package/lib/src/common/git.mjs +31 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +28 -2
- package/lib/src/common/package-json-helper.mjs +14 -4
- package/lib/src/common/project-meta.mjs +2 -2
- package/lib/src/common/template-compiler.mjs +2 -0
- package/lib/src/common/transpile-ts.mjs +94 -0
- package/lib/src/create-app/context-builder.mjs +1 -0
- package/lib/src/create-app/git-manager.mjs +12 -2
- package/lib/src/create-app/json-manager.mjs +21 -18
- package/lib/src/create-app/new-app-prompt.mjs +4 -2
- package/lib/src/create-app/success-message.mjs +1 -1
- package/lib/src/update-app/conflict-report-writer.mjs +33 -21
- package/lib/src/update-app/json-drift.mjs +54 -0
- package/lib/src/update-app/project-updater.mjs +123 -14
- package/lib/src/update-app/template-differ.mjs +5 -5
- package/lib/src/update-app/three-way-merge.mjs +42 -0
- package/lib/src/update-app/update-context-builder.mjs +1 -0
- package/lib/src/update-app/update-summary-printer.mjs +10 -2
- package/package.json +9 -8
- package/templates/base/AGENTS.md.hbs +42 -41
- package/templates/base/GETTING_STARTED.md.hbs +26 -20
- package/templates/base/README.md.hbs +7 -4
- package/templates/base/src/app/globals.d.ts.hbs +6 -3
- package/templates/base/src/app/guards.ts.hbs +1 -3
- package/templates/base/src/app/main.ts.hbs +2 -9
- package/templates/base/src/app/routes.ts.hbs +7 -2
- package/templates/base/src/app/views/home/home.view.html.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +461 -1
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +39 -48
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +36 -1
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +47 -77
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +286 -24
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +144 -26
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +66 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +135 -75
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +85 -4
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +93 -54
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.test.js.hbs +404 -0
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.ts.hbs +242 -0
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +70 -1
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +54 -15
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +12 -1
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +39 -36
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.test.js.hbs +179 -0
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.ts.hbs +93 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +135 -17
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +199 -81
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +40 -23
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +67 -65
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +17 -17
- package/templates/base/src/core/base-classes/component/component.ts.hbs +8 -41
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +1 -1
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +37 -39
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +14 -1
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +33 -31
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +32 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +21 -13
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +4 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +11 -8
- package/templates/base/src/core/index.ts.hbs +4 -2
- package/templates/base/src/core/internals.d.ts.hbs +199 -20
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +18 -0
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +26 -25
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/globals/globals.ts.hbs +7 -32
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +133 -50
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +147 -39
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +75 -19
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +73 -47
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +339 -232
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +37 -25
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +57 -16
- package/templates/base/src/core/services/i18n/i18n.ts.hbs +42 -42
- package/templates/base/src/core/services/i18n/languages.ts.hbs +0 -2
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +4 -6
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +10 -0
- package/templates/base/src/core/services/pipe-registry/pipes.ts.hbs +4 -2
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +183 -4
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +207 -84
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +26 -27
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +38 -60
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +4 -4
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +30 -30
- package/templates/base/src/core/services/router/router.test.js.hbs +306 -0
- package/templates/base/src/core/services/router/router.ts.hbs +113 -53
- package/templates/base/tools/builder/builder.config.js.hbs +6 -0
- package/templates/base/tools/builder/builder.js.hbs +18 -6
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +81 -6
- package/templates/base/tools/builder/core/app/copy-static.js.hbs +12 -1
- package/templates/base/tools/builder/core/app/handler-refs.js.hbs +114 -0
- package/templates/base/tools/builder/core/app/handler-refs.test.js.hbs +70 -0
- package/templates/base/tools/builder/core/app/paths.js.hbs +17 -0
- package/templates/base/tools/builder/core/app/route-keys.js.hbs +47 -0
- package/templates/base/tools/builder/core/app/route-keys.test.js.hbs +97 -0
- package/templates/base/tools/builder/core/app/ts-errors.js.hbs +58 -0
- package/templates/base/tools/builder/core/app/ts-errors.test.js.hbs +84 -0
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +21 -55
- package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -0
- package/templates/base/tools/builder/core/html-index/validate-html.js.hbs +10 -2
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +111 -23
- package/templates/base/tools/builder/core/html-templates/minify-templates.js.hbs +8 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +5 -3
- package/templates/base/tools/builder/core/html-templates/template-literal.js.hbs +193 -0
- package/templates/base/tools/builder/core/html-templates/template-literal.test.js.hbs +69 -0
- package/templates/base/tools/builder/core/i18n/build-i18n.js.hbs +14 -6
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.js.hbs +82 -0
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.test.js.hbs +65 -0
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +6 -6
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +15 -7
- package/templates/base/tools/builder/core/prod-bundle/hash-files.js.hbs +4 -4
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +31 -13
- package/templates/base/tools/builder/core/seo/generate-robots-txt.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +3 -3
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +43 -13
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +22 -7
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.test.js.hbs +30 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +28 -21
- package/templates/base/tools/builder/core/seo/html-substitutions.test.js.hbs +45 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +24 -6
- package/templates/base/tools/builder/core/seo/route-output.test.js.hbs +31 -0
- package/templates/base/tools/builder/core/seo/seo-routes.js.hbs +29 -0
- package/templates/base/tools/builder/core/seo/seo-routes.test.js.hbs +21 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +6 -7
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +34 -5
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +24 -11
- package/templates/base/tools/builder/core/styles/sass.js.hbs +3 -2
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
- package/templates/base/tools/dev/dev-serve.js.hbs +30 -5
- package/templates/base/tools/dev/serve.js.hbs +152 -22
- package/templates/base/tools/dev/watcher.js.hbs +34 -3
- package/templates/base/tools/generator/generator.js.hbs +1 -1
- package/templates/base/tools/generator/handle-file.js.hbs +4 -2
- package/templates/base/tools/generator/handle-file.test.js.hbs +3 -3
- package/templates/base/tools/generator/templates/component/component.template.js.hbs +3 -3
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +3 -3
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +1 -1
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +1 -1
- package/templates/base/tools/generator/templates/view/view.template.js.hbs +2 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +1 -1
- package/templates/base/tools/testin-nutin/core/globals/assertion-lib.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/{tests/assertions.test.js.hbs → globals/assertion-lib.test.js.hbs} +6 -0
- package/templates/base/tools/testin-nutin/core/globals/register-test-globals.js.hbs +6 -7
- package/templates/base/tools/testin-nutin/core/{tests/globals-each-all.test.js.hbs → globals/register-test-globals.test.js.hbs} +12 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +10 -4
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +8 -8
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.js.hbs +27 -25
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +30 -22
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.js.hbs +7 -10
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +12 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.js.hbs +47 -22
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +47 -26
- package/templates/base/tools/testin-nutin/mocks/mock-router.js.hbs +19 -11
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +24 -7
- package/templates/base/tools/testin-nutin/runner.js.hbs +12 -3
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +45 -20
- package/templates/base/tools/utils/build-lock.js.hbs +30 -2
- package/templates/base/tools/utils/ensure-deps.js.hbs +72 -0
- package/templates/base/tools/utils/ensure-deps.test.js.hbs +17 -0
- package/templates/base/tools/utils/format.js.hbs +1 -1
- package/templates/base/tools/utils/index.js.hbs +2 -1
- package/templates/base/tools/utils/print.js.hbs +2 -2
- package/templates/base/tools/utils/resolve-port.js.hbs +30 -0
- package/templates/base/tools/utils/resolve-port.test.js.hbs +47 -0
- package/templates/base/tools/utils/run-command.js.hbs +30 -1
- package/templates/base/tools/utils/run-command.test.js.hbs +12 -1
- package/templates/base/tools/utils/run-script.js.hbs +2 -2
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +3 -0
- package/templates/features/docker/tools/docker/nginx.conf.template +6 -4
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +1 -1
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +2 -2
- package/templates/features/docker/tools/docker/scripts/locale-redirects.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/locale-redirects.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.js.hbs +25 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +3 -1
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +12 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +21 -4
- package/templates/features/markdown/markdown-content/getting-started.md.hbs +28 -0
- package/templates/features/markdown/markdown-content/writing-pages.md.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.scss.hbs +75 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.test.js.hbs +40 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.ts.hbs +65 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.test.js.hbs +67 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.ts.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.scss.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.test.js.hbs +59 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.ts.hbs +72 -0
- package/templates/features/markdown/src/app/markdown/locales/en.json.hbs +6 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.test.js.hbs +63 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.test.js.hbs +78 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.test.js.hbs +98 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.ts.hbs +157 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.test.js.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.ts.hbs +50 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.test.js.hbs +74 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.ts.hbs +26 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.test.js.hbs +22 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.ts.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.html.hbs +4 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.scss.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.test.js.hbs +125 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.ts.hbs +146 -0
- package/templates/features/markdown/tools/builder/core/markdown/generate-markdown-pages.js.hbs +71 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.js.hbs +582 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.test.js.hbs +390 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.js.hbs +155 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.test.js.hbs +112 -0
- /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { BaseComponent
|
|
2
|
-
|
|
3
|
-
export interface ViewOptions extends BaseComponentOptions {
|
|
4
|
-
viewName: string;
|
|
5
|
-
}
|
|
1
|
+
import { BaseComponent } from '../base-component/base-component.js';
|
|
6
2
|
|
|
7
3
|
export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComponent<T> {
|
|
8
4
|
private _viewName: string;
|
|
9
5
|
protected routeParams: Record<string, string> = {};
|
|
10
|
-
private _template:
|
|
6
|
+
private _template: Nutin.Template;
|
|
11
7
|
|
|
12
8
|
constructor({
|
|
13
9
|
template,
|
|
@@ -15,7 +11,7 @@ export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComp
|
|
|
15
11
|
mountTarget = '#app',
|
|
16
12
|
viewName,
|
|
17
13
|
trustLevel,
|
|
18
|
-
}: ViewOptions) {
|
|
14
|
+
}: Nutin.ViewOptions) {
|
|
19
15
|
super({ mountTarget, tagName, trustLevel });
|
|
20
16
|
if (!viewName) throw new Error('View requires a viewName.');
|
|
21
17
|
this._template = template ?? '';
|
|
@@ -26,7 +22,7 @@ export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComp
|
|
|
26
22
|
return this._viewName;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
protected override generateTemplate():
|
|
25
|
+
protected override generateTemplate(): Nutin.Template {
|
|
30
26
|
return this._template;
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -46,6 +42,13 @@ export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComp
|
|
|
46
42
|
return key in this.routeParams && this.routeParams[key] !== undefined;
|
|
47
43
|
}
|
|
48
44
|
|
|
45
|
+
// The page's title, read by the router after each navigation (document.title). Override it
|
|
46
|
+
// for a title that depends on the route, e.g. the page being shown. Undefined: the router
|
|
47
|
+
// uses config/seo.json's title, then the "<viewName>.title" translation, then viewName.
|
|
48
|
+
public documentTitle(): string | undefined {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
49
52
|
// Navigation hooks — called by router only, never by render lifecycle
|
|
50
53
|
public onEnter(): void {}
|
|
51
54
|
public onExit(): void {}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// Abstract classes
|
|
1
|
+
// Abstract classes and helpers:
|
|
2
|
+
// Component, View, Service
|
|
2
3
|
export * from './base-classes/index.js';
|
|
3
4
|
|
|
4
|
-
// Core functionalities
|
|
5
|
+
// Core functionalities:
|
|
6
|
+
// EventBus and facades, global components, httpClient, i18n, pipeRegistry, router
|
|
5
7
|
export * from './services/index.js';
|
|
@@ -1,27 +1,206 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// Nutin's public types, all inside the global `Nutin` namespace so they can't collide with other packages' globals.
|
|
2
|
+
// No import needed: the app extends it in src/app/globals.d.ts.
|
|
3
|
+
// This file must stay free of top-level import/export, so runtime classes go through import().
|
|
4
|
+
declare namespace Nutin {
|
|
5
|
+
interface RouterEventMap {
|
|
6
|
+
'navigate': { path: string };
|
|
7
|
+
'reload': undefined;
|
|
8
|
+
}
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
10
|
+
interface LifecycleEventMap {
|
|
11
|
+
'before-render': undefined;
|
|
12
|
+
'after-render': undefined;
|
|
13
|
+
'before-destroy': undefined;
|
|
14
|
+
'after-destroy': undefined;
|
|
15
|
+
'view-mount': { viewName: string };
|
|
16
|
+
'view-unmount': { viewName: string };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface I18nEventMap {
|
|
20
|
+
'language-changed': {
|
|
21
|
+
lang: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface FrameworkEventMap extends RouterEventMap, LifecycleEventMap, I18nEventMap {}
|
|
26
|
+
|
|
27
|
+
interface AppEventMap {}
|
|
28
|
+
|
|
29
|
+
interface EventMap extends FrameworkEventMap, AppEventMap {}
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
type EventKey = keyof EventMap;
|
|
32
|
+
|
|
33
|
+
type Subscription<K extends EventKey = EventKey> = {
|
|
34
|
+
event: K;
|
|
35
|
+
callback: (data: EventMap[K]) => void;
|
|
36
|
+
once?: boolean;
|
|
18
37
|
};
|
|
19
|
-
}
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
// Components
|
|
40
|
+
|
|
41
|
+
type Template = string | import('./index.js').SafeHtml;
|
|
42
|
+
|
|
43
|
+
type TrustLevel = 'strict' | 'normal' | 'trusted';
|
|
44
|
+
|
|
45
|
+
interface ComponentConfig {
|
|
46
|
+
selector: string;
|
|
47
|
+
factory: (element: HTMLElement) => import('./index.js').Component;
|
|
48
|
+
// Same keep value on the next render: the child is kept as-is instead of recreated
|
|
49
|
+
keep?: string | number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface BaseComponentOptions {
|
|
53
|
+
template?: Template;
|
|
54
|
+
mountTarget?: string | HTMLElement;
|
|
55
|
+
tagName?: keyof HTMLElementTagNameMap;
|
|
56
|
+
trustLevel?: TrustLevel;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ComponentProps {
|
|
60
|
+
// Common HTML attributes - extend as needed
|
|
61
|
+
className?: string;
|
|
62
|
+
style?: string;
|
|
63
|
+
textContent?: string;
|
|
64
|
+
|
|
65
|
+
// Form field bindings - extend as needed
|
|
66
|
+
name?: string;
|
|
67
|
+
email?: string;
|
|
68
|
+
|
|
69
|
+
// Allow for additional data-bind attributes
|
|
70
|
+
[key: string]: any;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface ComponentOptions<K = any> extends BaseComponentOptions {
|
|
74
|
+
props?: ComponentProps;
|
|
75
|
+
config?: K;
|
|
76
|
+
defaults?: Partial<K>;
|
|
77
|
+
templateFn?: (config?: K) => Template;
|
|
78
|
+
normalizeKeys?: (keyof K)[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ViewOptions extends BaseComponentOptions {
|
|
82
|
+
viewName: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface CatalogConfig extends ComponentOptions {
|
|
86
|
+
items: CatalogItemConfig[];
|
|
87
|
+
elementName: string;
|
|
88
|
+
elementTag?: keyof HTMLElementTagNameMap;
|
|
89
|
+
selector: string;
|
|
90
|
+
component: new (el: HTMLElement, data: any, props?: any) => import('./index.js').Component;
|
|
91
|
+
// Same value on the next render keeps an unchanged item as-is; changed items are recreated
|
|
92
|
+
keepBy?: (item: any, index: number) => string | number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface CatalogItemBase {
|
|
96
|
+
index: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type CatalogItemObject<T extends object> = T & CatalogItemBase;
|
|
100
|
+
|
|
101
|
+
interface CatalogItemPrimitive extends CatalogItemBase {
|
|
102
|
+
value: string | number | boolean | null | undefined;
|
|
103
|
+
}
|
|
22
104
|
|
|
23
|
-
|
|
105
|
+
type CatalogItemConfig<T = any> =
|
|
106
|
+
T extends object ? CatalogItemObject<T> : CatalogItemPrimitive;
|
|
24
107
|
|
|
25
|
-
|
|
108
|
+
// Router
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Route guard function that receives route parameters and returns:
|
|
112
|
+
* - true to allow navigation
|
|
113
|
+
* - false to block it
|
|
114
|
+
* - string to redirect to a different route
|
|
115
|
+
*/
|
|
116
|
+
type RouteGuard = (params: Record<string, string>) => boolean | string | Promise<boolean | string>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a route's view. Returning a Promise makes the route lazy: e.g.
|
|
120
|
+
* `() => import('./views/about/about.view.js').then((m) => new m.AboutView())`
|
|
121
|
+
* puts the view's code in its own chunk, loaded on the first visit.
|
|
122
|
+
*/
|
|
123
|
+
type ViewFactory = () => import('./index.js').View | Promise<import('./index.js').View>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Route configuration - can be just a view factory or an object with guards
|
|
127
|
+
*/
|
|
128
|
+
type RouteConfig = ViewFactory | {
|
|
129
|
+
view: ViewFactory;
|
|
130
|
+
guards?: RouteGuard[];
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type Routes = Record<string, RouteConfig>;
|
|
134
|
+
|
|
135
|
+
interface RouteMatch {
|
|
136
|
+
route: RouteConfig;
|
|
137
|
+
params: Record<string, string>;
|
|
138
|
+
pattern: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Instance types of the framework services
|
|
142
|
+
type Router = ReturnType<typeof import('./index.js').AppRouter>;
|
|
143
|
+
type EventBus = InstanceType<typeof import('./index.js').EventBus>;
|
|
144
|
+
type HttpClient = InstanceType<typeof import('./index.js').HttpClient>;
|
|
145
|
+
|
|
146
|
+
// HTTP
|
|
147
|
+
|
|
148
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
149
|
+
|
|
150
|
+
type QueryValue = string | number | boolean | null | undefined;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* headers?: Record<string, string>;
|
|
154
|
+
* queryParams?: Record<string, QueryValue | QueryValue[]>;
|
|
155
|
+
* timeout?: number;
|
|
156
|
+
* signal?: AbortSignal;
|
|
157
|
+
* credentials? / cache? / referrerPolicy? / redirect?: passed to fetch as-is.
|
|
158
|
+
*/
|
|
159
|
+
interface RequestConfig {
|
|
160
|
+
headers?: Record<string, string>;
|
|
161
|
+
queryParams?: Record<string, QueryValue | QueryValue[]>;
|
|
162
|
+
timeout?: number;
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
credentials?: RequestCredentials;
|
|
165
|
+
cache?: RequestCache;
|
|
166
|
+
referrerPolicy?: ReferrerPolicy;
|
|
167
|
+
redirect?: RequestRedirect;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface HttpClientOptions {
|
|
171
|
+
// URL prefixes (origin + path) whose paths may contain encoded "/" or "\" (%2F, %5C).
|
|
172
|
+
trustedAPIs?: string[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// i18n & pipes
|
|
176
|
+
|
|
177
|
+
type Language = typeof import('./services/i18n/languages.js').LANGUAGES[number];
|
|
178
|
+
type Translations = Record<string, any>;
|
|
179
|
+
|
|
180
|
+
type PipeFunction = (value: any, ...args: any[]) => string;
|
|
181
|
+
|
|
182
|
+
// Globals
|
|
183
|
+
|
|
184
|
+
interface GlobalMountable {
|
|
185
|
+
render(): HTMLElement;
|
|
186
|
+
destroy?(): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface GlobalConfig<T extends GlobalMountable = GlobalMountable> {
|
|
190
|
+
/** The component's own class — constructed internally as `new component(mountTarget)`. */
|
|
191
|
+
component: new (mountTarget: HTMLElement) => T;
|
|
192
|
+
/** Stamped onto the mounted root element; pass this same id to `hideGlobals`/`revealGlobals`. */
|
|
193
|
+
id: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface RegisterGlobalsOptions {
|
|
197
|
+
/** Rendered and prepended to `<body>`, in the given order (e.g. a header). */
|
|
198
|
+
before?: GlobalConfig[];
|
|
199
|
+
/** Rendered and appended to `<body>`, in the given order (e.g. a footer). */
|
|
200
|
+
after?: GlobalConfig[];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
26
203
|
|
|
27
|
-
|
|
204
|
+
// Set by the builder's esbuild `define` so prod bundles can drop i18n code when it's
|
|
205
|
+
// disabled; undefined in tests and tsc output, which fall back to CONFIG.i18n.
|
|
206
|
+
declare var __NUTIN_I18N__: boolean | undefined;
|
|
@@ -175,6 +175,24 @@ describe('EventBus', () => {
|
|
|
175
175
|
expect(regularCalls).toBe(2);
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
it('keeps dispatching to the other listeners when one throws, and still removes a throwing once() handler', () => {
|
|
179
|
+
const errSpy = spyOn(console, 'error');
|
|
180
|
+
errSpy.andCallFake(() => {});
|
|
181
|
+
let received = 0;
|
|
182
|
+
let onceCalls = 0;
|
|
183
|
+
|
|
184
|
+
eventBus.once('notification', () => { onceCalls++; throw new Error('boom'); });
|
|
185
|
+
eventBus.subscribe('notification', () => { received++; });
|
|
186
|
+
|
|
187
|
+
eventBus.emit('notification', {});
|
|
188
|
+
eventBus.emit('notification', {});
|
|
189
|
+
|
|
190
|
+
expect(onceCalls).toBe(1);
|
|
191
|
+
expect(received).toBe(2);
|
|
192
|
+
expect(errSpy.callCount).toBe(1);
|
|
193
|
+
errSpy.restore();
|
|
194
|
+
});
|
|
195
|
+
|
|
178
196
|
it('off(event) with no callback removes every handler for that event', () => {
|
|
179
197
|
let callsA = 0;
|
|
180
198
|
let callsB = 0;
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import { Service } from "../../
|
|
1
|
+
import { Service } from "../../base-classes/service/service.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type Subscription<K extends EventKey = EventKey> = {
|
|
6
|
-
event: K;
|
|
7
|
-
callback: (data: EventMap[K]) => void;
|
|
8
|
-
once?: boolean;
|
|
9
|
-
};
|
|
3
|
+
// The payload is required unless the event declares none (`undefined` in its EventMap entry).
|
|
4
|
+
type EventArgs<K extends Nutin.EventKey> = undefined extends Nutin.EventMap[K] ? [data?: Nutin.EventMap[K]] : [data: Nutin.EventMap[K]];
|
|
10
5
|
|
|
11
6
|
export class EventBus extends Service<EventBus> {
|
|
12
|
-
private _subscriptions: Subscription[] = [];
|
|
7
|
+
private _subscriptions: Nutin.Subscription[] = [];
|
|
13
8
|
|
|
14
9
|
constructor() {
|
|
15
10
|
super();
|
|
@@ -17,29 +12,35 @@ export class EventBus extends Service<EventBus> {
|
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
private handlers: {
|
|
20
|
-
[K in EventKey]?: Array<(data: EventMap[K]) => void>;
|
|
15
|
+
[K in Nutin.EventKey]?: Array<(data: Nutin.EventMap[K]) => void>;
|
|
21
16
|
} = {};
|
|
22
17
|
|
|
23
|
-
public subscribe<K extends EventKey>(
|
|
18
|
+
public subscribe<K extends Nutin.EventKey>(
|
|
24
19
|
event: K,
|
|
25
|
-
callback: (data: EventMap[K]) => void
|
|
20
|
+
callback: (data: Nutin.EventMap[K]) => void
|
|
26
21
|
): void {
|
|
27
22
|
this.addHandler(event, callback, false);
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
public once<K extends EventKey>(
|
|
25
|
+
public once<K extends Nutin.EventKey>(
|
|
31
26
|
event: K,
|
|
32
|
-
callback: (data: EventMap[K]) => void
|
|
27
|
+
callback: (data: Nutin.EventMap[K]) => void
|
|
33
28
|
): void {
|
|
34
29
|
this.addHandler(event, callback, true);
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
public emit<K extends EventKey>(event: K, data
|
|
32
|
+
public emit<K extends Nutin.EventKey>(event: K, ...[data]: EventArgs<K>): void {
|
|
38
33
|
const callbacks = this.handlers[event];
|
|
39
34
|
if (!callbacks) return;
|
|
40
35
|
|
|
41
36
|
callbacks.slice().forEach(callback => {
|
|
42
|
-
|
|
37
|
+
// A failing listener is reported but doesn't keep the others (the router's
|
|
38
|
+
// included) from receiving the event.
|
|
39
|
+
try {
|
|
40
|
+
callback(data as Nutin.EventMap[K]);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`Error in a "${event}" listener:`, error);
|
|
43
|
+
}
|
|
43
44
|
|
|
44
45
|
const subIndex = this._subscriptions.findIndex(
|
|
45
46
|
sub => sub.event === event && sub.callback === callback && sub.once
|
|
@@ -50,17 +51,17 @@ export class EventBus extends Service<EventBus> {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
public off<K extends EventKey>(
|
|
54
|
+
public off<K extends Nutin.EventKey>(
|
|
54
55
|
event: K,
|
|
55
|
-
callback?: (data: EventMap[K]) => void
|
|
56
|
+
callback?: (data: Nutin.EventMap[K]) => void
|
|
56
57
|
): void {
|
|
57
58
|
const handlers = this.handlers[event];
|
|
58
59
|
if (!handlers) return;
|
|
59
60
|
|
|
60
61
|
if (callback) {
|
|
61
62
|
(this.handlers as Record<
|
|
62
|
-
EventKey,
|
|
63
|
-
Array<(data: EventMap[K]) => void>
|
|
63
|
+
Nutin.EventKey,
|
|
64
|
+
Array<(data: Nutin.EventMap[K]) => void>
|
|
64
65
|
>)[event] = handlers.filter(h => h !== callback);
|
|
65
66
|
} else {
|
|
66
67
|
delete this.handlers[event];
|
|
@@ -71,18 +72,18 @@ export class EventBus extends Service<EventBus> {
|
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
private addHandler<K extends EventKey>(
|
|
75
|
+
private addHandler<K extends Nutin.EventKey>(
|
|
75
76
|
event: K,
|
|
76
|
-
callback: (data: EventMap[K]) => void,
|
|
77
|
+
callback: (data: Nutin.EventMap[K]) => void,
|
|
77
78
|
once: boolean
|
|
78
79
|
): void {
|
|
79
80
|
if (!this.handlers[event]) {
|
|
80
81
|
this.handlers[event] = [];
|
|
81
82
|
}
|
|
82
83
|
(
|
|
83
|
-
this.handlers as Record<EventKey, Array<(data: EventMap[K]) => void>>
|
|
84
|
+
this.handlers as Record<Nutin.EventKey, Array<(data: Nutin.EventMap[K]) => void>>
|
|
84
85
|
)[event].push(callback);
|
|
85
|
-
this._subscriptions.push({ event, callback, once } as Subscription);
|
|
86
|
+
this._subscriptions.push({ event, callback, once } as Nutin.Subscription);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
private cleanupEventListeners = (): void => {
|
|
@@ -93,4 +94,4 @@ export class EventBus extends Service<EventBus> {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
export const AppEventBus = EventBus.getInstance();
|
|
97
|
+
export const AppEventBus = /* @__PURE__ */ EventBus.getInstance();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppEventBus
|
|
1
|
+
import { AppEventBus } from "./event-bus.js";
|
|
2
2
|
|
|
3
3
|
class LifecycleFacade {
|
|
4
|
-
constructor(private bus:
|
|
4
|
+
constructor(private bus: Nutin.EventBus) {}
|
|
5
5
|
|
|
6
6
|
beforeRender(): void {
|
|
7
7
|
this.bus.emit('before-render');
|
|
@@ -58,4 +58,4 @@ class LifecycleFacade {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export const Lifecycle = new LifecycleFacade(AppEventBus);
|
|
61
|
+
export const Lifecycle = /* @__PURE__ */ new LifecycleFacade(AppEventBus);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppEventBus
|
|
1
|
+
import { AppEventBus } from "./event-bus.js";
|
|
2
2
|
|
|
3
3
|
class NavigationFacade {
|
|
4
|
-
constructor(private bus:
|
|
4
|
+
constructor(private bus: Nutin.EventBus) {}
|
|
5
5
|
|
|
6
6
|
navigateTo(path: string): void {
|
|
7
7
|
this.bus.emit('navigate', { path });
|
|
@@ -22,4 +22,4 @@ class NavigationFacade {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export const Navigation = new NavigationFacade(AppEventBus);
|
|
25
|
+
export const Navigation = /* @__PURE__ */ new NavigationFacade(AppEventBus);
|
|
@@ -1,41 +1,17 @@
|
|
|
1
|
-
import { Service } from '../../base-classes/
|
|
2
|
-
|
|
3
|
-
export interface GlobalMountable {
|
|
4
|
-
render(): HTMLElement;
|
|
5
|
-
destroy?(): void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface GlobalConfig<T extends GlobalMountable = GlobalMountable> {
|
|
9
|
-
/** The component's own class — constructed internally as `new component(mountTarget)`. */
|
|
10
|
-
component: new (mountTarget: HTMLElement) => T;
|
|
11
|
-
/** Stamped onto the mounted root element; pass this same id to `hideGlobals`/`revealGlobals`. */
|
|
12
|
-
id: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface RegisterGlobalsOptions {
|
|
16
|
-
/** Rendered and prepended to `<body>`, in the given order (e.g. a header). */
|
|
17
|
-
before?: GlobalConfig[];
|
|
18
|
-
/** Rendered and appended to `<body>`, in the given order (e.g. a footer). */
|
|
19
|
-
after?: GlobalConfig[];
|
|
20
|
-
}
|
|
1
|
+
import { Service } from '../../base-classes/service/service.js';
|
|
21
2
|
|
|
22
3
|
export class Globals extends Service<Globals> {
|
|
23
|
-
private _mounted: Record<string, GlobalMountable> = {};
|
|
4
|
+
private _mounted: Record<string, Nutin.GlobalMountable> = {};
|
|
24
5
|
private _originalDisplay: Record<string, string> = {};
|
|
25
6
|
|
|
26
7
|
constructor() { super(); }
|
|
27
8
|
|
|
28
|
-
public register({ before = [], after = [] }: RegisterGlobalsOptions): void {
|
|
29
|
-
//
|
|
30
|
-
// mountTarget param as HTMLElement, but DomHelper.mountElement only
|
|
31
|
-
// *appends* (rather than replacing) the target when mountTarget is a string
|
|
32
|
-
// selector. This cast satisfies the stricter constructor type while still
|
|
33
|
-
// passing the string 'body' through at runtime, so components append into
|
|
34
|
-
// <body> instead of replacing it.
|
|
9
|
+
public register({ before = [], after = [] }: Nutin.RegisterGlobalsOptions): void {
|
|
10
|
+
// Satisfies the stricter constructor type while letting components append into <body> instead of replacing it.
|
|
35
11
|
const mountTarget = 'body' as unknown as HTMLElement;
|
|
36
12
|
const isElement = (el: HTMLElement | null): el is HTMLElement => el !== null;
|
|
37
13
|
|
|
38
|
-
const mount = ({ component: Component, id }: GlobalConfig): HTMLElement | null => {
|
|
14
|
+
const mount = ({ component: Component, id }: Nutin.GlobalConfig): HTMLElement | null => {
|
|
39
15
|
if (this._mounted[id]) {
|
|
40
16
|
console.warn(`Global "${id}" is already registered - skipping.`);
|
|
41
17
|
return null;
|
|
@@ -80,13 +56,12 @@ export class Globals extends Service<Globals> {
|
|
|
80
56
|
/**
|
|
81
57
|
* Mounts components outside the router's render target (`<main id="app">`),
|
|
82
58
|
* e.g. a header/footer that should persist across every route. Each entry's
|
|
83
|
-
* `component` is constructed internally
|
|
84
|
-
* `mountTarget` themselves — and its rendered root element is given the
|
|
59
|
+
* `component` is constructed internally and its rendered root element is given the
|
|
85
60
|
* entry's `id`, so `hideGlobals(ids)`/`revealGlobals(ids)` can target it
|
|
86
61
|
* later by that same id. Registering an `id` that's already registered logs
|
|
87
62
|
* a `console.warn` and skips it.
|
|
88
63
|
*/
|
|
89
|
-
export const registerGlobals = (options: RegisterGlobalsOptions): void => {
|
|
64
|
+
export const registerGlobals = (options: Nutin.RegisterGlobalsOptions): void => {
|
|
90
65
|
Globals.getInstance().register(options);
|
|
91
66
|
};
|
|
92
67
|
|