@nutin/cli 1.3.0 → 1.3.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/CHANGELOG.md +34 -0
- package/README.md +8 -9
- package/lib/index.mjs +18 -18
- package/lib/src/context-builder.mjs +21 -16
- package/lib/src/print.mjs +1 -0
- package/lib/src/project-generator.mjs +5 -5
- package/lib/src/prompts.mjs +13 -85
- package/lib/src/version.mjs +1 -1
- package/package.json +1 -1
- package/templates/base/builder.config.js.hbs +1 -0
- package/templates/base/src/app/globals.d.ts.hbs +12 -8
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +25 -8
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +34 -20
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +34 -0
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +11 -12
- package/templates/base/src/core/base-classes/component/component.ts.hbs +13 -17
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +2 -1
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +0 -33
- package/templates/base/src/core/services/router/router.ts.hbs +0 -1
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +35 -7
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +31 -8
- package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
- package/templates/base/src/libs/popover/popover.ts.hbs +84 -91
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +6 -3
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
- package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
- package/templates/base/src/styles/core/_index.scss.hbs +6 -0
- package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
- package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
- package/templates/base/src/styles/main.scss.hbs +1 -1
- package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +41 -8
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +14 -0
- package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## V1.3.1
|
|
4
|
+
|
|
5
|
+
- Minor features:
|
|
6
|
+
- New BaseComponent protected method.
|
|
7
|
+
Register events that will trigger re-render. Call this in component's constructor.
|
|
8
|
+
```ts
|
|
9
|
+
// force = true: calls forceRender() instead of render()
|
|
10
|
+
listenToRenderEvents(events: EventKey[], force: boolean = false): void
|
|
11
|
+
```
|
|
12
|
+
- data-optional now supports JS values
|
|
13
|
+
```html
|
|
14
|
+
data-optional="${myValue}"
|
|
15
|
+
```
|
|
16
|
+
- PopoverView and AnchorComponents now have accessibility features
|
|
17
|
+
- ButtonManager now handles checkboxes as well
|
|
18
|
+
- i18nService now emits `language-changed` event and exposes related methods
|
|
19
|
+
|
|
20
|
+
- Fixes:
|
|
21
|
+
- CatalogConfig no longer renders multiple containers when render() or forceRender() are triggered by events
|
|
22
|
+
- Event Listeners are now destroyed properly before triggering a re-render
|
|
23
|
+
- Router now correctly redirects to 404 page when URL starts with two slashes `//`.
|
|
24
|
+
- Popover no longer flickers on close
|
|
25
|
+
- Snackbar messages are now properly sanitized
|
|
26
|
+
|
|
27
|
+
- CLI
|
|
28
|
+
- Rework: App creation flow
|
|
29
|
+
- No features enabled by default
|
|
30
|
+
- Presets `--preset <minimal|standard|full|cicd>`
|
|
31
|
+
Minimal: External templates
|
|
32
|
+
Standard: Minimal + i18n & built-in SCSS utilities.
|
|
33
|
+
Full: Standard + deployment helpers & built-in testing toolkit
|
|
34
|
+
CI/CD: Minimal + deployment helpers
|
|
35
|
+
- Remaining flags: `--i18n`, `--deploy-helper`, `--testin-nutin`, `--transition`
|
|
36
|
+
|
|
3
37
|
## V1.3.0
|
|
4
38
|
|
|
5
39
|
Version 1.3.0 marks a stability milestone with various improvements and refinements, making this the recommended version for new projects.
|
package/README.md
CHANGED
|
@@ -39,19 +39,18 @@ npx @nutin/cli
|
|
|
39
39
|
# Package manager
|
|
40
40
|
-pm --package-manager <manager> # npm (Default), yarn, pnpm, bun
|
|
41
41
|
|
|
42
|
+
# Presets
|
|
43
|
+
--preset <minimal|standard|full|cicd>
|
|
44
|
+
# minimal: external templates
|
|
45
|
+
# standard: minimal + i18n & built-in SCSS classes
|
|
46
|
+
# full: standard + deployment helpers & built-in testing toolkit
|
|
47
|
+
# cicd: minimal + deployment helpers
|
|
48
|
+
|
|
42
49
|
# Features
|
|
43
|
-
--template # (Default) Use external templates
|
|
44
|
-
--no-template # Use inline templates
|
|
45
|
-
--stylin-nutin # (Default) Use built-in SCSS utility classes & mixins
|
|
46
|
-
--no-stylin-nutin # Do not use built-in SCSS utility
|
|
47
|
-
--testin-nutin # Use testin-nutin toolkit
|
|
48
|
-
--no-testin-nutin # (Default) Do not use testin-nutin toolkit
|
|
49
50
|
--i18n # Use i18n & json-based content
|
|
50
|
-
--no-i18n # (Default) Do not use i18n & json-based content
|
|
51
51
|
--deploy-helper # Use Docker & deployment helpers
|
|
52
|
-
--
|
|
52
|
+
--testin-nutin # Use testin-nutin toolkit
|
|
53
53
|
--transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
|
|
54
|
-
--no-transition # (Default) Do not use animated view transitions
|
|
55
54
|
```
|
|
56
55
|
|
|
57
56
|
## [First app tutorial](https://www.nutin.org/tutorial)
|
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { program } from 'commander';
|
|
2
|
-
import { print } from './src/print.mjs';
|
|
2
|
+
import { print, chalk } from './src/print.mjs';
|
|
3
3
|
import { promptUser } from './src/prompts.mjs';
|
|
4
4
|
import { createProject } from './src/project-generator.mjs';
|
|
5
5
|
import { displaySuccessMessage } from './src/utils.mjs';
|
|
@@ -8,34 +8,34 @@ import { packageVersion } from './src/version.mjs';
|
|
|
8
8
|
export async function createApp() {
|
|
9
9
|
program
|
|
10
10
|
.version(packageVersion)
|
|
11
|
+
.configureOutput({
|
|
12
|
+
helpWidth: 100
|
|
13
|
+
})
|
|
11
14
|
.argument('[projectName]', 'Name of the project')
|
|
12
|
-
.option('-d, --defaults', 'Use default options')
|
|
13
15
|
.option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm, bun)')
|
|
14
|
-
.option('--
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
.option('--preset <preset>',
|
|
17
|
+
`${chalk.yellow('Project preset configuration:')}
|
|
18
|
+
${chalk.cyan('• minimal')}${chalk.boldGray(' - external templates')}
|
|
19
|
+
${chalk.cyan('• standard')}${chalk.boldGray(' - minimal + i18n & built-in SCSS utilities')}
|
|
20
|
+
${chalk.cyan('• full')}${chalk.boldGray(' - standard + deployment helpers & built-in testing toolkit')}
|
|
21
|
+
${chalk.cyan('• cicd')}${chalk.boldGray(' - minimal + deployment helpers')}`
|
|
22
|
+
)
|
|
18
23
|
.option('--i18n', 'Use i18n & json-based content')
|
|
19
|
-
.option('--no-i18n', '(Default) Do not use i18n & json-based content')
|
|
20
24
|
.option('--deploy-helper', 'Use Docker & deployment helpers')
|
|
21
|
-
.option('--
|
|
22
|
-
.option('--
|
|
23
|
-
.option('--no-testin-nutin', '(Default) Do not use testin-nutin toolkit')
|
|
24
|
-
.option('--transition', 'Use animated view transitions\nNote: May interfere with CSS `position: fixed`, `z-index`...')
|
|
25
|
-
.option('--no-transition', '(Default) Do not use animated view transitions')
|
|
26
|
-
.addHelpText('afterAll', '\nChaining options :\nExample: -d -pm npm --i18n --testinNutin')
|
|
25
|
+
.option('--testin-nutin', 'Use built-in testing toolkit')
|
|
26
|
+
.option('--transition', 'Use animated view transitions\nNote: may interfere with CSS `position: fixed`, `z-index`…')
|
|
27
27
|
.action(async (projectName, cliOptions) => {
|
|
28
28
|
print.blue('🚀 Welcome to nutin !');
|
|
29
|
-
|
|
30
29
|
try {
|
|
31
|
-
const
|
|
32
|
-
await createProject(
|
|
33
|
-
displaySuccessMessage(
|
|
30
|
+
const preferences = await promptUser(projectName, cliOptions);
|
|
31
|
+
await createProject(preferences);
|
|
32
|
+
displaySuccessMessage(preferences);
|
|
34
33
|
} catch (error) {
|
|
35
|
-
print.boldError(`❌ Error creating project: ${error.
|
|
34
|
+
print.boldError(`❌ Error creating project: ${error.stack}`);
|
|
36
35
|
process.exit(1);
|
|
37
36
|
}
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
program.parse();
|
|
41
40
|
}
|
|
41
|
+
|
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import { getCiCommand } from './package-manager.mjs';
|
|
2
2
|
import { packageVersion } from './version.mjs';
|
|
3
3
|
|
|
4
|
+
const PRESET_MAP = {
|
|
5
|
+
default: { template: false, stylinNutin: false, i18n: false, deployHelper: false, testinNutin: false },
|
|
6
|
+
minimal: { template: true, stylinNutin: false, i18n: false, deployHelper: false, testinNutin: false },
|
|
7
|
+
standard: { template: true, stylinNutin: true, i18n: true, deployHelper: false, testinNutin: false },
|
|
8
|
+
full: { template: true, stylinNutin: true, i18n: true, deployHelper: true, testinNutin: true },
|
|
9
|
+
cicd: { template: true, stylinNutin: false, i18n: false, deployHelper: true, testinNutin: false },
|
|
10
|
+
};
|
|
11
|
+
|
|
4
12
|
export const defaults = {
|
|
5
13
|
projectName: 'my-nutin-app',
|
|
6
14
|
packageManager: 'npm',
|
|
7
|
-
|
|
8
|
-
template: true,
|
|
9
|
-
i18n: false,
|
|
10
|
-
deployHelper: false,
|
|
11
|
-
testinNutin: false,
|
|
12
|
-
transition: false
|
|
15
|
+
...PRESET_MAP['default']
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
export class ContextBuilder {
|
|
16
|
-
buildContext(
|
|
19
|
+
buildContext(preferences) {
|
|
17
20
|
const version = packageVersion;
|
|
18
|
-
const ciCommand = getCiCommand(
|
|
21
|
+
const ciCommand = getCiCommand(preferences.packageManager);
|
|
22
|
+
|
|
23
|
+
const preset = PRESET_MAP[preferences.preset] ?? PRESET_MAP['default'];
|
|
19
24
|
|
|
20
25
|
return {
|
|
21
|
-
projectName:
|
|
22
|
-
packageManager:
|
|
26
|
+
projectName: preferences.projectName,
|
|
27
|
+
packageManager: preferences.packageManager,
|
|
23
28
|
|
|
24
|
-
template:
|
|
25
|
-
stylinNutin:
|
|
26
|
-
i18n:
|
|
27
|
-
deployHelper:
|
|
28
|
-
testinNutin:
|
|
29
|
-
transition:
|
|
29
|
+
template: preset.template,
|
|
30
|
+
stylinNutin: preset.stylinNutin,
|
|
31
|
+
i18n: preferences.i18n ?? preset.i18n,
|
|
32
|
+
deployHelper: preferences.deployHelper ?? preset.deployHelper,
|
|
33
|
+
testinNutin: preferences.testinNutin ?? preset.testinNutin,
|
|
34
|
+
transition: preferences.transition,
|
|
30
35
|
|
|
31
36
|
ciCommand: ciCommand,
|
|
32
37
|
version: version
|
package/lib/src/print.mjs
CHANGED
|
@@ -16,10 +16,10 @@ export class ProjectGenerator {
|
|
|
16
16
|
this.builder = new ContextBuilder();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async createProject(
|
|
20
|
-
const { projectName } =
|
|
19
|
+
async createProject(preferences) {
|
|
20
|
+
const { projectName } = preferences;
|
|
21
21
|
const projectPath = path.join(process.cwd(), projectName);
|
|
22
|
-
const context = this.builder.buildContext(
|
|
22
|
+
const context = this.builder.buildContext(preferences);
|
|
23
23
|
|
|
24
24
|
await this.validateProjectPath(projectPath, projectName);
|
|
25
25
|
await this.setupProjectStructure(projectPath, context);
|
|
@@ -48,6 +48,6 @@ export class ProjectGenerator {
|
|
|
48
48
|
|
|
49
49
|
export const projectGenerator = new ProjectGenerator();
|
|
50
50
|
|
|
51
|
-
export async function createProject(
|
|
52
|
-
return projectGenerator.createProject(
|
|
51
|
+
export async function createProject(preferences) {
|
|
52
|
+
return projectGenerator.createProject(preferences);
|
|
53
53
|
}
|
package/lib/src/prompts.mjs
CHANGED
|
@@ -5,112 +5,40 @@ export async function promptUser(initialName, cliOptions = {}) {
|
|
|
5
5
|
|
|
6
6
|
let projectName = initialName;
|
|
7
7
|
if (!projectName) {
|
|
8
|
-
const
|
|
8
|
+
const nameInput = await inquirer.prompt([
|
|
9
9
|
{
|
|
10
10
|
type: 'input',
|
|
11
11
|
name: 'projectName',
|
|
12
12
|
message: 'Project name:',
|
|
13
13
|
default: defaults.projectName,
|
|
14
|
-
validate: validateProjectName
|
|
15
|
-
}
|
|
14
|
+
validate: validateProjectName,
|
|
15
|
+
},
|
|
16
16
|
]);
|
|
17
|
-
projectName =
|
|
17
|
+
projectName = nameInput.projectName;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
let packageManager = cliOptions.packageManager;
|
|
21
21
|
if (!packageManager) {
|
|
22
|
-
const
|
|
22
|
+
const pmPreference = await inquirer.prompt([
|
|
23
23
|
{
|
|
24
24
|
type: 'list',
|
|
25
25
|
name: 'packageManager',
|
|
26
26
|
message: 'Package manager:',
|
|
27
27
|
choices: ['npm', 'yarn', 'pnpm', 'bun'],
|
|
28
|
-
default: defaults.packageManager
|
|
29
|
-
}
|
|
28
|
+
default: defaults.packageManager,
|
|
29
|
+
},
|
|
30
30
|
]);
|
|
31
|
-
packageManager =
|
|
31
|
+
packageManager = pmPreference.packageManager;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (cliOptions.defaults) {
|
|
35
|
-
return {
|
|
36
|
-
projectName,
|
|
37
|
-
packageManager,
|
|
38
|
-
stylinNutin: cliOptions.stylinNutin,
|
|
39
|
-
template: cliOptions.template,
|
|
40
|
-
i18n: cliOptions.i18n,
|
|
41
|
-
deployHelper: cliOptions.deployHelper,
|
|
42
|
-
testinNutin: cliOptions.testinNutin,
|
|
43
|
-
transition: cliOptions.transitions
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const questions = [];
|
|
48
|
-
|
|
49
|
-
if (cliOptions.stylinNutin === undefined) {
|
|
50
|
-
questions.push({
|
|
51
|
-
type: 'confirm',
|
|
52
|
-
name: 'stylinNutin',
|
|
53
|
-
message: 'Use built-in SCSS utility classes ?',
|
|
54
|
-
default: defaults.stylinNutin
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (cliOptions.template === undefined) {
|
|
59
|
-
questions.push({
|
|
60
|
-
type: 'confirm',
|
|
61
|
-
name: 'template',
|
|
62
|
-
message: 'Use external templates ?',
|
|
63
|
-
default: defaults.template
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (cliOptions.i18n === undefined) {
|
|
68
|
-
questions.push({
|
|
69
|
-
type: 'confirm',
|
|
70
|
-
name: 'i18n',
|
|
71
|
-
message: 'Use i18n with json-based content ?',
|
|
72
|
-
default: defaults.i18n
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (cliOptions.deployHelper === undefined) {
|
|
77
|
-
questions.push({
|
|
78
|
-
type: 'confirm',
|
|
79
|
-
name: 'deployHelper',
|
|
80
|
-
message: 'Use Docker & deployment helpers ?',
|
|
81
|
-
default: defaults.deployHelper
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (cliOptions.testinNutin === undefined) {
|
|
86
|
-
questions.push({
|
|
87
|
-
type: 'confirm',
|
|
88
|
-
name: 'testinNutin',
|
|
89
|
-
message: 'Use testin-nutin toolkit?',
|
|
90
|
-
default: defaults.testinNutin
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (cliOptions.transition === undefined) {
|
|
95
|
-
questions.push({
|
|
96
|
-
type: 'confirm',
|
|
97
|
-
name: 'transition',
|
|
98
|
-
message: 'Use animated view transitions?',
|
|
99
|
-
default: defaults.transition
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const answers = questions.length > 0 ? await inquirer.prompt(questions) : {};
|
|
104
|
-
|
|
105
34
|
return {
|
|
106
35
|
projectName,
|
|
107
36
|
packageManager,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
transition:
|
|
113
|
-
testinNutin: cliOptions.testinNutin ?? answers.testinNutin
|
|
37
|
+
preset: cliOptions.preset,
|
|
38
|
+
i18n: cliOptions.i18n,
|
|
39
|
+
deployHelper: cliOptions.deployHelper,
|
|
40
|
+
testinNutin: cliOptions.testinNutin,
|
|
41
|
+
transition: cliOptions.transition,
|
|
114
42
|
};
|
|
115
43
|
}
|
|
116
44
|
|
package/lib/src/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = '1.3.
|
|
1
|
+
export const packageVersion = '1.3.1';
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
declare type CoreEventMap = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
'navigate': string;
|
|
3
|
+
'reload': string;
|
|
4
|
+
'view-mount': string;
|
|
5
|
+
'view-render': string;
|
|
6
|
+
'view-unmount': string;
|
|
7
|
+
'track-pageview': { page: string };
|
|
8
|
+
'popover-opened': string;
|
|
9
|
+
'popover-close': string;
|
|
10
|
+
{{#if i18n}}
|
|
11
|
+
'language-changed': string;
|
|
12
|
+
{{/if}}
|
|
13
|
+
// Add other events and their payload types here
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
declare type StoreEventMap = {[ K in `store:${string}` ]: any; };
|
|
@@ -6,19 +6,20 @@ 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
|
-
import {
|
|
10
|
-
|
|
9
|
+
import { Component, ComponentOptions, AppEventBus } from '../../index.js';
|
|
10
|
+
import { SecurityHelper, TrustLevel } from './helpers/security.helper.js';
|
|
11
|
+
export { CatalogItemConfig, CatalogConfig, CatalogItemPrimitive } from './helpers/catalog.helper.js';
|
|
11
12
|
|
|
12
13
|
/**```typescript
|
|
13
14
|
* export interface ComponentConfig {
|
|
14
15
|
* selector: string;
|
|
15
|
-
* factory: (element: HTMLElement) =>
|
|
16
|
+
* factory: (element: HTMLElement) => Component;
|
|
16
17
|
* }
|
|
17
18
|
* ```
|
|
18
19
|
*/
|
|
19
20
|
export interface ComponentConfig {
|
|
20
21
|
selector: string;
|
|
21
|
-
factory: (element: HTMLElement) =>
|
|
22
|
+
factory: (element: HTMLElement) => Component;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface BaseComponentOptions {
|
|
@@ -31,7 +32,8 @@ export interface BaseComponentOptions {
|
|
|
31
32
|
export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
|
|
32
33
|
private _children: BaseComponent[] = [];
|
|
33
34
|
protected element: T;
|
|
34
|
-
protected eventListeners: Array<[string, EventListener]> = [];
|
|
35
|
+
protected eventListeners: Array<[EventTarget, string, EventListener]> = [];
|
|
36
|
+
private _options: ComponentOptions;
|
|
35
37
|
|
|
36
38
|
constructor({
|
|
37
39
|
template = '',
|
|
@@ -40,6 +42,7 @@ export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
|
|
|
40
42
|
trustLevel
|
|
41
43
|
}: BaseComponentOptions) {
|
|
42
44
|
this.element = DomHelper.createElement<T>(tagName, template, trustLevel);
|
|
45
|
+
this._options = { template, mountTarget, tagName, trustLevel };
|
|
43
46
|
this.parseDataAttributes();
|
|
44
47
|
this.autoBindEvents();
|
|
45
48
|
DomHelper.mountElement(this.element, mountTarget);
|
|
@@ -50,19 +53,20 @@ export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
public destroy(): void {
|
|
53
|
-
EventHelper.destroyEvents(this.
|
|
56
|
+
EventHelper.destroyEvents(this.eventListeners);
|
|
54
57
|
ChildrenHelper.destroyChildren(this._children);
|
|
55
58
|
this.element.remove();
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
public render(): HTMLElement {
|
|
59
62
|
this.addChildren();
|
|
63
|
+
DomHelper.cleanupOptionalContent();
|
|
60
64
|
return this.element;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/**
|
|
64
68
|
* ```typescript
|
|
65
|
-
interface CatalogConfig {
|
|
69
|
+
interface CatalogConfig extends ComponentOptions {
|
|
66
70
|
array: CatalogItemConfig[];
|
|
67
71
|
elementName: string;
|
|
68
72
|
elementTag?: keyof HTMLElementTagNameMap;
|
|
@@ -72,7 +76,7 @@ interface CatalogConfig {
|
|
|
72
76
|
```
|
|
73
77
|
*/
|
|
74
78
|
protected catalogConfig(config: CatalogConfig): ComponentConfig[] {
|
|
75
|
-
return CatalogHelper.generateCatalog(config);
|
|
79
|
+
return CatalogHelper.generateCatalog(config, this.element);
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
protected parseDataAttributes(): void {
|
|
@@ -83,10 +87,23 @@ interface CatalogConfig {
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
protected autoBindEvents(): void {
|
|
90
|
+
EventHelper.destroyEvents(this.eventListeners);
|
|
91
|
+
this.eventListeners.length = 0;
|
|
86
92
|
EventHelper.bindEvents(this, this.element, this.eventListeners);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
protected addChildren(): void {
|
|
90
96
|
ChildrenHelper.addChildren(this, this.element, this._children);
|
|
91
97
|
}
|
|
98
|
+
|
|
99
|
+
protected forceRender(): void {
|
|
100
|
+
this.element.innerHTML = SecurityHelper.sanitizeTemplate(this._options.template, this._options.trustLevel);
|
|
101
|
+
this.render();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected listenToRenderEvents(events: EventKey[], force: boolean = false): void {
|
|
105
|
+
events.forEach((event: EventKey) => {
|
|
106
|
+
AppEventBus.subscribe(event, () => force ? this.forceRender() : this.render());
|
|
107
|
+
})
|
|
108
|
+
}
|
|
92
109
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, ComponentConfig, ComponentOptions } from "../../index.js";
|
|
2
2
|
|
|
3
|
-
export interface CatalogConfig {
|
|
3
|
+
export interface CatalogConfig extends ComponentOptions {
|
|
4
4
|
array: CatalogItemConfig[];
|
|
5
5
|
elementName: string;
|
|
6
6
|
elementTag?: keyof HTMLElementTagNameMap;
|
|
7
7
|
selector: string;
|
|
8
|
-
component: new (el: HTMLElement, data: any) =>
|
|
8
|
+
component: new (el: HTMLElement, data: any, props?: any) => Component;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export interface CatalogItemBase {
|
|
@@ -23,12 +23,12 @@ export type CatalogItemConfig<T = any> =
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* ```typescript
|
|
26
|
-
interface CatalogConfig {
|
|
26
|
+
interface CatalogConfig extends ComponentOptions {
|
|
27
27
|
array: CatalogItemConfig[];
|
|
28
28
|
elementName: string;
|
|
29
29
|
elementTag?: keyof HTMLElementTagNameMap;
|
|
30
30
|
selector: string;
|
|
31
|
-
component: new (el: HTMLElement, data: any) =>
|
|
31
|
+
component: new (el: HTMLElement, data: any) => Component;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
type CatalogItemConfig<T = any> =
|
|
@@ -36,42 +36,56 @@ type CatalogItemConfig<T = any> =
|
|
|
36
36
|
```
|
|
37
37
|
*/
|
|
38
38
|
export class CatalogHelper {
|
|
39
|
-
public static generateCatalog(config: CatalogConfig): ComponentConfig[] {
|
|
39
|
+
public static generateCatalog(config: CatalogConfig, scope: Element = document.documentElement): ComponentConfig[] {
|
|
40
40
|
if (!config.array || config.array.length < 1) return [];
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const componentConfigs: ComponentConfig[] = [];
|
|
43
|
+
const containers = scope.querySelectorAll(`[data-catalog="${config.selector}"]`);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
containers.forEach((container) => {
|
|
46
|
+
if (!container || !(container instanceof HTMLElement) || (container.firstElementChild)) return;
|
|
47
|
+
componentConfigs.push(...this.getComponentConfigArray(config, container))
|
|
48
|
+
})
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
return componentConfigs;
|
|
51
|
+
}
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
private static getComponentConfigArray(config: CatalogConfig, container: HTMLElement): ComponentConfig[] {
|
|
54
|
+
const componentConfigs: ComponentConfig[] = [];
|
|
55
|
+
for (let i = 0; i < config.array.length; i++) {
|
|
49
56
|
this.createElements(i, config, container);
|
|
50
57
|
this.pushConfig(i, componentConfigs, config);
|
|
51
58
|
}
|
|
52
|
-
|
|
53
59
|
return componentConfigs;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
private static createElements(index: number, config: CatalogConfig, container: HTMLElement): void {
|
|
57
|
-
const
|
|
63
|
+
const wrapper = document.createElement(config.elementTag || 'div');
|
|
64
|
+
const el = document.createElement('div');
|
|
65
|
+
wrapper.appendChild(el);
|
|
58
66
|
el.setAttribute('data-component', `${config.elementName}-${index}`);
|
|
59
|
-
|
|
60
|
-
container?.appendChild(
|
|
67
|
+
wrapper.dataset.index = String(index);
|
|
68
|
+
container?.appendChild(wrapper);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
private static pushConfig(index: number, componentConfigs: ComponentConfig[], config: CatalogConfig): void {
|
|
64
|
-
const
|
|
72
|
+
const configWithIndex = this.getConfigWithIndex(config, index)
|
|
73
|
+
|
|
74
|
+
const { props, defaults, normalizeKeys } = config;
|
|
75
|
+
const options = { ...props, ...defaults, ...normalizeKeys };
|
|
65
76
|
|
|
66
|
-
const configWithIndex: CatalogItemConfig = (item && typeof item === 'object')
|
|
67
|
-
? { ...(item as object), index: index }
|
|
68
|
-
: { value: item, index: index };
|
|
69
|
-
|
|
70
77
|
componentConfigs.push(
|
|
71
78
|
{
|
|
72
79
|
selector: `${config.elementName}-${index}`,
|
|
73
|
-
factory: (el) => new config.component(el, configWithIndex)
|
|
80
|
+
factory: (el) => new config.component(el, configWithIndex, options),
|
|
74
81
|
},
|
|
75
82
|
)
|
|
76
83
|
}
|
|
84
|
+
|
|
85
|
+
private static getConfigWithIndex(config: CatalogConfig, index: number): CatalogItemConfig {
|
|
86
|
+
const item = config.array[index];
|
|
87
|
+
return (item && typeof item === 'object')
|
|
88
|
+
? { ...(item as object), index: index }
|
|
89
|
+
: { value: item, index: index };
|
|
90
|
+
}
|
|
77
91
|
}
|
|
@@ -24,6 +24,40 @@ export class DomHelper {
|
|
|
24
24
|
element.innerHTML = SecurityHelper.sanitizeTemplate(template, trustLevel);
|
|
25
25
|
return element;
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
public static cleanupOptionalContent(): void {
|
|
29
|
+
const isEmpty = (el: HTMLElement): boolean => {
|
|
30
|
+
if (el instanceof HTMLImageElement) {
|
|
31
|
+
return !el.src || el.src.trim() === "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
35
|
+
return !el.value?.trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (el instanceof HTMLMediaElement || el instanceof HTMLSourceElement) {
|
|
39
|
+
return !el.getAttribute("src");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = el.textContent?.trim();
|
|
43
|
+
return !content || content === "undefined";
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isValueUndefined = (el: HTMLElement): boolean => {
|
|
47
|
+
const value = el.getAttribute("data-optional");
|
|
48
|
+
if (!value) return false;
|
|
49
|
+
return (
|
|
50
|
+
value.trim() === '' ||
|
|
51
|
+
value === "undefined" ||
|
|
52
|
+
value === "null"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
document.querySelectorAll<HTMLElement>("[data-optional]").forEach(el => {
|
|
57
|
+
if (isValueUndefined(el) || isEmpty(el)) el.remove();
|
|
58
|
+
el.removeAttribute("data-optional");
|
|
59
|
+
});
|
|
60
|
+
}
|
|
27
61
|
|
|
28
62
|
private static appendOrReplace(config: DomElementConfig) {
|
|
29
63
|
if (config.target instanceof HTMLElement) {
|