@nutin/cli 1.1.0 → 1.2.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +69 -6
  2. package/README.md +49 -2
  3. package/lib/index.mjs +9 -9
  4. package/lib/src/file-generator.mjs +24 -9
  5. package/lib/src/package-manager.mjs +72 -51
  6. package/lib/src/prompts.mjs +10 -10
  7. package/lib/src/utils.mjs +4 -1
  8. package/package.json +4 -3
  9. package/templates/base/.dockerignore.hbs +12 -2
  10. package/templates/base/README.md.hbs +23 -0
  11. package/templates/base/builder.config.js.hbs +28 -0
  12. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +11 -12
  13. package/templates/base/src/app/dom.test.js.hbs +8 -1
  14. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +2 -1
  15. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +11 -11
  16. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +3 -0
  17. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +3 -0
  18. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +5 -4
  19. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +55 -27
  20. package/templates/base/src/core/services/http-client/http-client.ts.hbs +24 -17
  21. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +3 -3
  22. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +2 -2
  23. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +25 -10
  24. package/templates/base/src/core/services/router/router.ts.hbs +1 -0
  25. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +4 -3
  26. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +1 -1
  27. package/templates/base/src/index.html.hbs +2 -2
  28. package/templates/base/src/libs/popover/popover.ts.hbs +2 -1
  29. package/templates/base/src/styles/main.scss.hbs +4 -1
  30. package/templates/base/tools/builder/builder.js.hbs +19 -26
  31. package/templates/base/tools/builder/core/copy-static.js.hbs +41 -20
  32. package/templates/base/tools/builder/core/esbuild.js.hbs +26 -0
  33. package/templates/base/tools/builder/core/finalize-build.js.hbs +24 -0
  34. package/templates/base/tools/builder/core/minify-html.js.hbs +60 -0
  35. package/templates/base/tools/builder/core/paths.js.hbs +19 -0
  36. package/templates/base/tools/builder/core/sass.js.hbs +22 -0
  37. package/templates/base/tools/builder/core/tsc.js.hbs +12 -0
  38. package/templates/base/tools/builder/core/validate-html.js.hbs +32 -12
  39. package/templates/base/tools/deployment/Dockerfile.hbs +22 -0
  40. package/templates/base/tools/deployment/gzip.conf.hbs +19 -0
  41. package/templates/base/tools/deployment/nginx.conf.hbs +78 -0
  42. package/templates/base/tools/dev/dev-serve.js.hbs +32 -0
  43. package/templates/base/tools/dev/serve.js.hbs +23 -0
  44. package/templates/base/tools/{watcher.js.hbs → dev/watcher.js.hbs} +8 -8
  45. package/templates/base/tools/generator/generator.js.hbs +21 -6
  46. package/templates/base/tools/generator/handle-file.js.hbs +32 -2
  47. package/templates/base/tools/generator/templates/index.js.hbs +3 -0
  48. package/templates/base/tools/utils/export-flags.js.hbs +6 -0
  49. package/templates/base/tools/utils/get-files-recursive.js.hbs +1 -3
  50. package/templates/base/tools/utils/index.js.hbs +8 -1
  51. package/templates/base/tools/utils/prompt-boolean.js.hbs +15 -0
  52. package/templates/base/tools/utils/run-command.js.hbs +12 -0
  53. package/templates/base/tools/utils/run-script.js.hbs +22 -0
  54. package/templates/features/i18n/src/core/config.ts.hbs +3 -0
  55. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +2 -0
  56. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +10 -1
  57. package/templates/features/i18n/src/core/services/i18n/languages.ts.hbs +7 -0
  58. package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +12 -17
  59. package/templates/features/i18n/tools/utils/languages.js.hbs +7 -0
  60. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +1 -1
  61. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -0
  62. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  63. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +4 -0
  64. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +13 -26
  65. package/templates/features/testinNutin/{test → testin-nutin}/core/index.js.hbs +1 -1
  66. package/templates/features/testinNutin/testin-nutin/core/src/globals/assertion-lib.js.hbs +136 -0
  67. package/templates/features/testinNutin/testin-nutin/core/src/globals/jsdom-setup.js.hbs +101 -0
  68. package/templates/features/testinNutin/{test → testin-nutin}/core/src/globals/register-test-globals.js.hbs +15 -1
  69. package/templates/features/testinNutin/testin-nutin/core/src/globals/spyon.js.hbs +59 -0
  70. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/print.js.hbs +3 -1
  71. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-discovery.js.hbs +5 -1
  72. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-queue.js.hbs +3 -1
  73. package/templates/features/testinNutin/{test → testin-nutin}/core/tests/assertions.test.js.hbs +26 -2
  74. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +35 -0
  75. package/templates/features/testinNutin/{test → testin-nutin}/watch-tests.js.hbs +1 -1
  76. package/templates/features/testinNutin/testin-nutin.config.js.hbs +14 -0
  77. package/templates/base/Dockerfile.hbs +0 -13
  78. package/templates/base/tools/builder/core/sass.config.js.hbs +0 -17
  79. package/templates/base/tools/builder/docs.md.hbs +0 -9
  80. package/templates/features/testinNutin/test/core/src/globals/assertion-lib.js.hbs +0 -118
  81. package/templates/features/testinNutin/test/core/src/globals/spyon.js.hbs +0 -22
  82. package/templates/features/testinNutin/test/core/src/jsdom-setup.js.hbs +0 -12
  83. package/templates/features/testinNutin/test/docs.md.hbs +0 -39
  84. package/templates/features/testinNutin/test/runner.js.hbs +0 -36
  85. /package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/queue.js.hbs +0 -0
  86. /package/templates/features/testinNutin/{test → testin-nutin}/core/tests/globals-each-all.test.js.hbs +0 -0
  87. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/create-mock-method.js.hbs +0 -0
  88. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-event-bus.js.hbs +0 -0
  89. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-http-client.js.hbs +0 -0
  90. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-i18n.js.hbs +0 -0
  91. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-router.js.hbs +0 -0
  92. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-store.js.hbs +0 -0
@@ -2,21 +2,19 @@ import { AnchorManager, Component, IAnchorConfig } from '../../../../core/index.
2
2
 
3
3
  /**
4
4
  * ```typescript
5
- interface IAttributesConfig {
6
- {{#if i18n}}
7
- i18nKey?: string;
8
- {{/if}}
5
+ * interface IAnchorConfig {
6
+ // prefix href with '#' for internal anchor
7
+ href: string;
8
+ target?: string;
9
9
  textContent?: string;
10
+ {{#if i18n}}
11
+ i18nKey?: string;
12
+ {{/if}}
10
13
  className?: string;
11
14
  style?: string;
12
- // use regular pipe syntax for arguments / chaining
15
+ // use regular pipe syntax for arguments / chaining
13
16
  pipes?: string;
14
- }
15
- interface IAnchorConfig extends IAttributesConfig {
16
- // prefix href with '#' for internal anchor
17
- href: string;
18
- // _blank, etc
19
- target?: string;
17
+ tagName?: keyof HTMLElementTagNameMap;
20
18
  }
21
19
  ```
22
20
  */
@@ -25,9 +23,10 @@ export class AnchorComponent extends Component<HTMLAnchorElement, IAnchorConfig>
25
23
  constructor(mountTarget: HTMLElement, config: IAnchorConfig) {
26
24
  super({
27
25
  mountTarget,
26
+ tagName: config.tagName,
28
27
  config,
29
28
  normalizeKeys: ['style', 'pipes', 'className']
30
29
  });
31
30
  new AnchorManager(config, this.element);
32
31
  }
33
- }
32
+ }
@@ -1,6 +1,13 @@
1
1
  describe('App container', () => {
2
- it('should exist', () => {
2
+ it('should be defined', () => {
3
3
  const appContainer = document.getElementById('app');
4
4
  expect(appContainer).toBeDefined();
5
5
  });
6
+ it('should have title', () => {
7
+ expect(document.title).toEqual('{{projectName}}');
8
+ });
9
+ it('should have meta description', () => {
10
+ const desc = document.querySelector('meta[name="description"]').content;
11
+ expect(desc).toEqual('{{projectName}}');
12
+ });
6
13
  });
@@ -6,6 +6,7 @@ import { I18nHelper } from './helpers/i18n.helper.js';
6
6
  import { PipeHelper } from './helpers/pipe.helper.js';
7
7
  import { ChildrenHelper } from './helpers/children.helper.js';
8
8
  import { CatalogHelper, CatalogConfig } from './helpers/catalog.helper.js';
9
+ export { CatalogItemConfig } from './helpers/catalog.helper.js';
9
10
 
10
11
  /**```typescript
11
12
  * export interface ComponentConfig {
@@ -59,7 +60,7 @@ export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
59
60
  /**
60
61
  * ```typescript
61
62
  interface CatalogConfig {
62
- array: any[];
63
+ array: CatalogItemConfig[];
63
64
  elementName: string;
64
65
  elementTag?: keyof HTMLElementTagNameMap;
65
66
  selector: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseComponent, ComponentConfig } from "../base-component.js";
2
2
 
3
3
  export interface CatalogConfig {
4
- array: any[];
4
+ array: CatalogItemConfig[];
5
5
  elementName: string;
6
6
  elementTag?: keyof HTMLElementTagNameMap;
7
7
  selector: string;
@@ -9,7 +9,7 @@ export interface CatalogConfig {
9
9
  };
10
10
 
11
11
  export interface CatalogItemBase {
12
- idx: number;
12
+ index: number;
13
13
  }
14
14
 
15
15
  export type CatalogItemObject<T extends object> = T & CatalogItemBase;
@@ -24,7 +24,7 @@ export type CatalogItemConfig<T = any> =
24
24
  /**
25
25
  * ```typescript
26
26
  interface CatalogConfig {
27
- array: any[];
27
+ array: CatalogItemConfig[];
28
28
  elementName: string;
29
29
  elementTag?: keyof HTMLElementTagNameMap;
30
30
  selector: string;
@@ -53,23 +53,23 @@ export class CatalogHelper {
53
53
  return componentConfigs;
54
54
  }
55
55
 
56
- private static createElements(idx: number, config: CatalogConfig, container: HTMLElement) {
56
+ private static createElements(index: number, config: CatalogConfig, container: HTMLElement) {
57
57
  const el = document.createElement(config.elementTag || 'div');
58
- el.setAttribute('data-component', `${config.elementName}-${idx}`);
59
- el.dataset.index = String(idx);
58
+ el.setAttribute('data-component', `${config.elementName}-${index}`);
59
+ el.dataset.index = String(index);
60
60
  container?.appendChild(el);
61
61
  }
62
62
 
63
- private static pushConfig(idx: number, componentConfigs: ComponentConfig[], config: CatalogConfig) {
64
- const item = config.array[idx];
63
+ private static pushConfig(index: number, componentConfigs: ComponentConfig[], config: CatalogConfig) {
64
+ const item = config.array[index];
65
65
 
66
66
  const configWithIndex: CatalogItemConfig = (item && typeof item === 'object')
67
- ? { ...(item as object), index: idx }
68
- : { value: item, index: idx };
67
+ ? { ...(item as object), index: index }
68
+ : { value: item, index: index };
69
69
 
70
70
  componentConfigs.push(
71
71
  {
72
- selector: `${config.elementName}-${idx}`,
72
+ selector: `${config.elementName}-${index}`,
73
73
  factory: (el) => new config.component(el, configWithIndex)
74
74
  },
75
75
  )
@@ -10,6 +10,9 @@ export class EventHelper {
10
10
  element.querySelectorAll('[data-event]').forEach(el => {
11
11
  const parts = el.getAttribute('data-event')!.split(':');
12
12
  const [eventName, handlerName, argsString] = parts;
13
+
14
+ if (!handlerName || !eventName) return;
15
+
13
16
  const handler = (component as any)[handlerName];
14
17
 
15
18
  if (typeof handler === 'function') {
@@ -28,6 +28,9 @@ export class PipeHelper {
28
28
 
29
29
  for (const pipe of pipes) {
30
30
  const [pipeName, ...argParts] = pipe.split(':');
31
+
32
+ if(!pipeName) return;
33
+
31
34
  const args = argParts.length ? argParts.join(':').split(',') : [];
32
35
  value = AppPipeRegistry.apply(pipeName.trim(), value, args.map(a => a.trim()));
33
36
  }
@@ -36,6 +36,7 @@ export class TokenHelper {
36
36
 
37
37
  public static resolve(token: string, el: Element, event: Event): any {
38
38
  const htmlEl = el as HTMLElement;
39
+ event.preventDefault();
39
40
 
40
41
  return (
41
42
  this.resolveExact(token, htmlEl, event) ??
@@ -52,9 +53,9 @@ export class TokenHelper {
52
53
  }
53
54
 
54
55
  private static resolvePrefixed(token: string, el: HTMLElement): any | null {
55
- for (const prefix in PREFIXED_TOKEN_RESOLVERS) {
56
+ for (const [prefix, resolver] of Object.entries(PREFIXED_TOKEN_RESOLVERS)) {
56
57
  if (token.startsWith(prefix)) {
57
- return PREFIXED_TOKEN_RESOLVERS[prefix](token.slice(prefix.length), el);
58
+ return resolver(token.slice(prefix.length), el);
58
59
  }
59
60
  }
60
61
  return null;
@@ -81,7 +82,7 @@ export class TokenHelper {
81
82
  }
82
83
 
83
84
  /** Register a new exact token like "@foo" */
84
- public static registerToken(
85
+ public static registerCustomToken(
85
86
  name: string,
86
87
  resolver: (el: HTMLElement, ev: Event) => any
87
88
  ) {
@@ -89,7 +90,7 @@ export class TokenHelper {
89
90
  }
90
91
 
91
92
  /** Register a new prefixed token like "@style:" */
92
- public static registerPrefixed(
93
+ public static registerPrefixedToken(
93
94
  prefix: string,
94
95
  resolver: (suffix: string, el: HTMLElement) => any
95
96
  ) {
@@ -1,63 +1,91 @@
1
1
  import { Service } from "../../index.js";
2
2
 
3
- /**
4
- IEventBus is a type alias for the instance of EventBus, not a true interface
5
- */
6
3
  export type IEventBus = InstanceType<typeof EventBus>;
7
4
 
5
+ type Subscription<K extends EventKey = EventKey> = {
6
+ event: K;
7
+ callback: (data: EventMap[K]) => void;
8
+ once?: boolean;
9
+ };
10
+
8
11
  export class EventBus extends Service<EventBus> {
9
- private _subscriptions: EventKey[] = [];
10
-
12
+ private _subscriptions: Subscription[] = [];
13
+
11
14
  constructor() {
12
15
  super();
13
16
  this.registerCleanup(this.cleanupEventListeners);
14
17
  }
15
18
 
16
19
  private handlers: {
17
- [K in EventKey]?: Array<(data: EventMap[K]) => void>
20
+ [K in EventKey]?: Array<(data: EventMap[K]) => void>;
18
21
  } = {};
19
-
20
- public subscribe<K extends EventKey>(event: K, callback: (data: EventMap[K]) => void): void {
21
- AppEventBus.on(event, callback);
22
- this._subscriptions.push(event);
22
+
23
+ public subscribe<K extends EventKey>(
24
+ event: K,
25
+ callback: (data: EventMap[K]) => void
26
+ ): void {
27
+ this.addHandler(event, callback, false);
23
28
  }
24
29
 
25
- private on<K extends EventKey>(event: K, callback: (data: EventMap[K]) => void): void {
30
+ public once<K extends EventKey>(
31
+ event: K,
32
+ callback: (data: EventMap[K]) => void
33
+ ): void {
34
+ this.addHandler(event, callback, true);
35
+ }
36
+
37
+ private addHandler<K extends EventKey>(
38
+ event: K,
39
+ callback: (data: EventMap[K]) => void,
40
+ once: boolean
41
+ ) {
26
42
  if (!this.handlers[event]) {
27
43
  this.handlers[event] = [];
28
44
  }
29
45
  this.handlers[event]!.push(callback);
46
+ this._subscriptions.push({ event, callback, once });
30
47
  }
31
48
 
32
- public emit<K extends EventKey>(event: K, data: EventMap[K]): void;
33
- public emit<K extends EventKey>(event: K): void;
34
49
  public emit<K extends EventKey>(event: K, data?: EventMap[K]): void {
35
- this._subscriptions.push(event);
36
50
  const callbacks = this.handlers[event];
37
- if (callbacks) {
38
- callbacks.forEach(callback => callback(data!));
39
- }
51
+ if (!callbacks) return;
52
+
53
+ callbacks.slice().forEach(callback => {
54
+ callback(data!);
55
+
56
+ const subIndex = this._subscriptions.findIndex(
57
+ sub => sub.event === event && sub.callback === callback && sub.once
58
+ );
59
+ if (subIndex > -1) {
60
+ this.off(event, callback);
61
+ }
62
+ });
40
63
  }
41
64
 
42
- public off<K extends EventKey>(event: K, callback: (data: EventMap[K]) => void): void {
65
+ public off<K extends EventKey>(
66
+ event: K,
67
+ callback?: (data: EventMap[K]) => void
68
+ ): void {
43
69
  const handlers = this.handlers[event];
44
70
  if (!handlers) return;
45
71
 
46
- this.handlers[event] = handlers.filter(handler =>
47
- handler !== callback
48
- ) as typeof handlers;
72
+ if (callback) {
73
+ this.handlers[event] = handlers.filter(h => h !== callback);
74
+ } else {
75
+ delete this.handlers[event];
76
+ }
77
+
78
+ this._subscriptions = this._subscriptions.filter(
79
+ sub => sub.event !== event || (callback && sub.callback !== callback)
80
+ );
49
81
  }
50
82
 
51
83
  private cleanupEventListeners = () => {
52
- this._subscriptions.forEach(event => {
53
- AppEventBus.off(event, () => {});
84
+ this._subscriptions.forEach(({ event, callback }) => {
85
+ this.off(event, callback);
54
86
  });
55
87
  this._subscriptions = [];
56
88
  };
57
-
58
- onDestroy<T extends Service<T>>(this: () => T): void {
59
-
60
- }
61
89
  }
62
90
 
63
91
  export const AppEventBus = EventBus.getInstance();
@@ -2,27 +2,34 @@ import { Service } from "../../index.js";
2
2
  import { HttpBuilder, HttpMethod, IRequestConfig } from "./helpers/http-builder.helper.js";
3
3
  import { HttpManager } from "./helpers/http-manager.helper.js";
4
4
 
5
- /**
6
- * IHttpClient is a type alias for the instance of HttpClient, not a true interface
7
- */
8
5
  export type IHttpClient = InstanceType<typeof HttpClient>;
9
6
 
10
7
  export class HttpClient extends Service<HttpClient> {
11
8
  private _baseUrl: string;
12
9
  private _defaultHeaders: Record<string, string>;
13
10
 
11
+ private _requestInterceptors: Array<(url: string, options: RequestInit) => void> = [];
12
+ private _responseInterceptors: Array<(response: Response) => void> = [];
13
+
14
14
  constructor(baseUrl: string = '', defaultHeaders: Record<string, string> = {}) {
15
15
  super();
16
16
  this._baseUrl = baseUrl;
17
- this._defaultHeaders = {
18
- 'Content-Type': 'application/json',
19
- ...defaultHeaders
20
- };
17
+ this._defaultHeaders = { 'Content-Type': 'application/json', ...defaultHeaders };
18
+ }
19
+
20
+ public addRequestInterceptor(fn: (url: string, options: RequestInit) => void) {
21
+ this._requestInterceptors.push(fn);
22
+ }
23
+
24
+ public addResponseInterceptor(fn: (response: Response) => void) {
25
+ this._responseInterceptors.push(fn);
21
26
  }
22
27
 
23
28
  onDestroy(): void {
24
29
  this._baseUrl = '';
25
30
  this._defaultHeaders = {};
31
+ this._requestInterceptors = [];
32
+ this._responseInterceptors = [];
26
33
  }
27
34
 
28
35
  private async request<T>(
@@ -39,8 +46,12 @@ export class HttpClient extends Service<HttpClient> {
39
46
  const url = HttpBuilder.buildRequestUrl(fullUrl, config?.queryParams);
40
47
  const requestOptions = HttpBuilder.buildRequestOptions(method, data, mergedConfig, controller.signal);
41
48
 
49
+ this._requestInterceptors.forEach(fn => fn(url.toString(), requestOptions));
50
+
42
51
  const response = await fetch(url.toString(), requestOptions);
43
-
52
+
53
+ this._responseInterceptors.forEach(fn => fn(response));
54
+
44
55
  await HttpManager.validateResponse(response);
45
56
  return await HttpManager.parseSuccessResponse<T>(response);
46
57
  } catch (error) {
@@ -50,23 +61,19 @@ export class HttpClient extends Service<HttpClient> {
50
61
  }
51
62
  }
52
63
 
53
- public async get<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
64
+ public get<T = unknown>(endpoint: string, config?: IRequestConfig) {
54
65
  return this.request<T>('GET', endpoint, null, config);
55
66
  }
56
-
57
- public async post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
67
+ public post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
58
68
  return this.request<T>('POST', endpoint, data, config);
59
69
  }
60
-
61
- public async put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
70
+ public put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
62
71
  return this.request<T>('PUT', endpoint, data, config);
63
72
  }
64
-
65
- public async patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
73
+ public patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
66
74
  return this.request<T>('PATCH', endpoint, data, config);
67
75
  }
68
-
69
- public async delete<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
76
+ public delete<T = unknown>(endpoint: string, config?: IRequestConfig) {
70
77
  return this.request<T>('DELETE', endpoint, undefined, config);
71
78
  }
72
79
  }
@@ -68,11 +68,11 @@ export class NavigationManager {
68
68
 
69
69
  const params: Record<string, string> = {};
70
70
  paramNames.forEach((name, i) => {
71
- if (match[i + 1]) {
72
- params[name] = match[i + 1];
71
+ const value = match[i + 1];
72
+ if (value) {
73
+ params[name] = value;
73
74
  }
74
75
  });
75
-
76
76
  return params;
77
77
  }
78
78
  }
@@ -58,12 +58,12 @@ export class RouteGuardsManager {
58
58
  const guardResult = await this.runGuards(guards, params);
59
59
 
60
60
  if (guardResult === false) {
61
- console.log(`Navigation to ${targetPath} blocked by route guard`);
61
+ // console.log(`Navigation to ${targetPath} blocked by route guard`);
62
62
  return { allowed: false };
63
63
  }
64
64
 
65
65
  if (typeof guardResult === 'string') {
66
- console.log(`Navigation to ${targetPath} redirected to ${guardResult} by route guard`);
66
+ // console.log(`Navigation to ${targetPath} redirected to ${guardResult} by route guard`);
67
67
  return { allowed: false, redirectTo: guardResult };
68
68
  }
69
69
 
@@ -56,25 +56,40 @@ export class ViewRenderManager {
56
56
  }
57
57
 
58
58
  static cleanupOptionalContent() {
59
- const isEmpty = (content: string | undefined | null): boolean => {
60
- if (!content?.trim() || content === 'undefined') return true;
61
- return false;
62
- };
59
+ const isEmpty = (el: HTMLElement): boolean => {
60
+ const attrName = el.dataset.optional?.trim();
63
61
 
64
- document.querySelectorAll<HTMLElement>('[data-optional]').forEach(el => {
65
- const content = el.textContent?.trim();
66
-
67
- if (isEmpty(content)) {
68
- el.remove();
62
+ if (attrName && attrName !== "") {
63
+ const attrValue = el.getAttribute(attrName);
64
+ return !attrValue || attrValue.trim() === "" || attrValue === "undefined";
65
+ }
66
+
67
+ if (el instanceof HTMLImageElement) {
68
+ return !el.src || el.src.trim() === "";
69
+ }
70
+
71
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
72
+ return !el.value?.trim();
73
+ }
74
+
75
+ if (el instanceof HTMLMediaElement || el instanceof HTMLSourceElement) {
76
+ return !el.getAttribute("src");
69
77
  }
78
+
79
+ const content = el.textContent?.trim();
80
+ return !content || content === "undefined";
81
+ };
82
+
83
+ document.querySelectorAll<HTMLElement>("[data-optional]").forEach(el => {
84
+ if (isEmpty(el)) el.remove();
70
85
  });
71
86
  }
72
87
 
73
88
  static emitEvent(event: EventKey, viewName: string): void {
74
89
  AppEventBus.emit(event, viewName);
75
90
  }
76
-
77
91
  {{#if transition}}
92
+
78
93
  private static delay(ms: number): Promise<void> {
79
94
  return new Promise(res => setTimeout(res, ms));
80
95
  }
@@ -5,6 +5,7 @@ import { NavigationManager } from './helpers/navigation-manager.helper.js';
5
5
 
6
6
  // centralized export
7
7
  export { Routes, RouteGuard } from './helpers/route-guard-manager.helper.js';
8
+ export { ViewRenderManager } from './helpers/view-render-manager.helper.js';
8
9
 
9
10
  /**
10
11
  * IRouter is a type alias for the instance of Router, not a true interface
@@ -3,9 +3,9 @@ import { AttributesHelper, IAttributesConfig } from "../helpers/attributes.helpe
3
3
  /**
4
4
  * ```typescript
5
5
  interface IAttributesConfig {
6
- {{#if i18n}}
6
+ {{#if i18n}}
7
7
  i18nKey?: string;
8
- {{/if}}
8
+ {{/if}}
9
9
  textContent?: string;
10
10
  className?: string;
11
11
  style?: string;
@@ -15,14 +15,15 @@ interface IAttributesConfig {
15
15
  interface IAnchorConfig extends IAttributesConfig {
16
16
  // prefix href with '#' for internal anchor
17
17
  href: string;
18
- // _blank, etc
19
18
  target?: string;
19
+ tagName?: keyof HTMLElementTagNameMap;
20
20
  }
21
21
  ```
22
22
  */
23
23
  export interface IAnchorConfig extends IAttributesConfig {
24
24
  href: string;
25
25
  target?: string;
26
+ tagName?: keyof HTMLElementTagNameMap;
26
27
  }
27
28
 
28
29
  export class AnchorManager {
@@ -109,7 +109,7 @@ describe('ButtonManager', () => {
109
109
 
110
110
  it('should set button className correctly', () => {
111
111
  const mockComponent = {};
112
- const manager = new ButtonManager(mockComponent, [], options);
112
+ const manager = new ButtonManager(mockComponent, []);
113
113
  const btn = {
114
114
  i18nKey: 'save',
115
115
  className: 'btn-primary'
@@ -6,9 +6,9 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <link rel="stylesheet" href="/main.css">
8
8
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
9
+ <title>{{projectName}}</title>
9
10
  </head>
10
11
  <body>
11
- <main id="app"></main>
12
- <script type="module" src="/app/main.js"></script>
12
+ <main id="app"></main>
13
13
  </body>
14
14
  </html>
@@ -1,4 +1,4 @@
1
- import { View, ButtonManager, BaseButton } from '../../core/index.js';
1
+ import { View, ButtonManager, BaseButton, ViewRenderManager } from '../../core/index.js';
2
2
 
3
3
  export interface PopoverButton extends BaseButton {
4
4
  // if needed for Popover-specific properties
@@ -59,6 +59,7 @@ export class PopoverView extends View {
59
59
  private createContent(): HTMLElement {
60
60
  const content = super.render();
61
61
  content.classList.add('popover-content');
62
+ ViewRenderManager.cleanupOptionalContent();
62
63
  return content;
63
64
  }
64
65
 
@@ -12,5 +12,8 @@
12
12
  {{#if stylinNutin}}
13
13
  // custom classes
14
14
  @use "custom-classes";
15
-
15
+ // components styles
16
+ @use "components";
17
+ // views styles
18
+ @use "views";
16
19
  {{/if}}
@@ -1,37 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { fileURLToPath } from 'url';
4
- import { dirname, resolve } from 'path';
5
- import { spawnSync } from 'child_process';
6
- import process from 'process';
7
- import { print } from '../utils/index.js';
3
+ import path from 'path';
4
+ import { print, isProd, runScript } from '../utils/index.js';
8
5
 
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = dirname(__filename);
6
+ const scriptsDir = path.join(process.cwd(),'tools', 'builder', 'core');
11
7
 
12
- import { BINARY_EXTENSIONS } from './variables/binary-extensions.js';
8
+ if (!isProd) print.boldInfo('For production, use build:prod.\n');
9
+ print.boldHead(`Starting build...\n`);
13
10
 
14
- print.boldHead(`Building static files...`);
15
- print.info(`File types that will be copied: ${Array.from(BINARY_EXTENSIONS)}`);
11
+ runScript(path.join(scriptsDir, 'copy-static.js'), 'Copying files...');
16
12
 
17
- function runScript(path, message) {
18
- print.boldSection(`\n${message}`);
19
- const result = spawnSync('node', [resolve(__dirname, path)], { stdio: 'inherit' });
13
+ if(!isProd) runScript(path.join(scriptsDir, 'tsc.js'), 'Running TypeScript compiler...');
20
14
 
21
- if (result.status !== 0) {
22
- print.boldError(`\nScript ${path} failed.`);
23
- process.exit(result.status ?? 1);
24
- }
25
- }
26
-
27
- runScript('./core/copy-static.js', 'Copying files...');
28
- {{#if i18n}}
29
- runScript('./core/build-i18n.js', 'Combining locales for production...');
30
- {{/if}}
31
15
  {{#if template}}
32
- runScript('./core/merge-templates.js', 'Merging HTML templates in JavaScript files...');
16
+ runScript(path.join(scriptsDir, 'merge-templates.js'), 'Merging HTML templates in temp files...');
17
+ {{else}}
18
+ runScript(path.join(scriptsDir, 'minify-html.js'), 'Minifying inline templates...');
33
19
  {{/if}}
34
- runScript('./core/sass.config.js', 'Compiling styles from main.scss...');
35
- runScript('./core/validate-html.js', 'Validating tags in index.html...');
20
+ {{#if i18n}}
21
+ runScript(path.join(scriptsDir, 'build-i18n.js'), 'Combining locales for production...');
22
+ {{/if}}
23
+ runScript(path.join(scriptsDir, 'sass.js'), 'Compiling styles from main.scss...');
24
+ runScript(path.join(scriptsDir, 'validate-html.js'), 'Validating tags in index.html...');
25
+
26
+ if (isProd) runScript(path.join(scriptsDir, 'esbuild.js'), 'Running esbuild...');
27
+
28
+ runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...')
36
29
 
37
30
  print.boldSuccess(`\nBuild successful!\n`);