semmet-angular 0.36.0 → 0.38.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.
Files changed (32) hide show
  1. package/README.en.md +13 -4
  2. package/README.md +13 -4
  3. package/README.pt-BR.md +13 -4
  4. package/package.json +2 -2
  5. package/src/accordion/files/__name@dasherize__/__name@dasherize__.css.template +0 -2
  6. package/src/block/files/__name@dasherize__/__name@dasherize__.css.template +103 -0
  7. package/src/block/files/__name@dasherize__/__name@dasherize__.html.template +209 -0
  8. package/src/block/files/__name@dasherize__/__name@dasherize__.ts.template +86 -0
  9. package/src/block/index.d.ts +1 -0
  10. package/src/block/index.js +6 -0
  11. package/src/block/index.js.map +1 -0
  12. package/src/block/index.ts +3 -0
  13. package/src/block/schema.d.ts +7 -0
  14. package/src/block/schema.js +3 -0
  15. package/src/block/schema.js.map +1 -0
  16. package/src/block/schema.json +51 -0
  17. package/src/block/schema.ts +23 -0
  18. package/src/button/files/__name@dasherize__/__name@dasherize__.css.template +0 -5
  19. package/src/card/files/__name@dasherize__/__name@dasherize__.css.template +0 -2
  20. package/src/carousel/files/__name@dasherize__/__name@dasherize__.css.template +0 -2
  21. package/src/collection.json +5 -0
  22. package/src/disclosure/files/__name@dasherize__/__name@dasherize__.css.template +0 -2
  23. package/src/form/files/__name@dasherize__/__name@dasherize__.css.template +1 -5
  24. package/src/list-group/files/__name@dasherize__/__name@dasherize__.css.template +0 -3
  25. package/src/navbar/files/__name@dasherize__/__name@dasherize__.css.template +0 -2
  26. package/src/ng-add/schema.d.ts +1 -0
  27. package/src/ng-add/schema.json +5 -0
  28. package/src/ng-add/schema.ts +1 -0
  29. package/src/utils/generate-composition.d.ts +8 -0
  30. package/src/utils/generate-composition.js +102 -0
  31. package/src/utils/generate-composition.js.map +1 -0
  32. package/src/utils/generate-composition.ts +121 -0
package/README.en.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # semmet-angular
2
2
 
3
- **Accessible UI Delivery Kit for Angular.**
3
+ **Accessible UI delivery dev kit in Angular.**
4
4
 
5
- `semmet-angular` is a delivery-focused schematic collection for Angular teams that want to ship accessible UI faster: components with real ARIA behavior, a consistent visual identity, and generated test files from the first command.
5
+ `semmet-angular` is a delivery-focused schematic collection for Angular teams that want to ship accessible UI faster: components with real ARIA behavior, validated compositions/blocks, and generated test files from the first command.
6
6
 
7
- Semmet Angular helps teams generate, test, verify, and package accessible Angular UI using the Angular CLI ecosystem.
7
+ Semmet Angular helps teams generate, compose, test, verify, and package accessible Angular UI using the Angular CLI ecosystem.
8
8
 
9
- **Gerar. Testar. Verificar. Empacotar.**
9
+ **Generate. Compose. Test. Verify. Package.**
10
10
 
11
11
  Read the broader product direction in [PRODUCT_VISION.md](./PRODUCT_VISION.md).
12
12
 
@@ -19,6 +19,7 @@ Brazilian Portuguese documentation is available in [README.md](./README.md).
19
19
  ```
20
20
  ng add semmet-angular
21
21
  ng generate semmet-angular:accordion my-faq
22
+ ng generate semmet-angular:block section-card
22
23
  ng generate semmet-angular:ci
23
24
  ng generate semmet-angular:docker
24
25
  ng generate semmet-angular:deploy
@@ -26,6 +27,14 @@ ng generate semmet-angular:deploy
26
27
 
27
28
  `ng add semmet-angular` installs the package and configures the delivery workflow once for the target application, including Playwright e2e support, npm scripts, and `npm run verify`. Each generated component is dropped into your project exactly like `ng generate component` would (respecting your `angular.json` project, source root, and selector prefix), and has **zero runtime dependency** on this package — `semmet-angular` is only needed at generation time.
28
29
 
30
+ ## Layered Architecture
31
+
32
+ Use isolated components when you need fine-grained control, or compositions when you want to accelerate accessible screens with validated structure.
33
+
34
+ - **Components**: isolated schematics such as `button`, `card`, `accordion`, `table`, and `navbar`.
35
+ - **Compositions / Blocks**: ready-made sections such as `section-card`, `section-faq`, `section-pricing`, `section-stats`, `section-table`, `section-form`, `section-tabs`, `section-list-group`, `section-alert`, `section-progress`, `section-carousel`, `header-navbar`, `footer-navigation`, and `command-list`.
36
+ - **Delivery**: `ng add`, `ci`, `docker`, `deploy`, and `verify`.
37
+
29
38
  ## Generated tests
30
39
 
31
40
  Every component schematic generates test files next to the component:
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # semmet-angular
2
2
 
3
- **Kit de entrega de UI acessível para Angular.**
3
+ **Dev kit de entrega de UI acessível em Angular.**
4
4
 
5
- `semmet-angular` é uma coleção de schematics focada em entrega para times Angular que querem publicar interfaces acessíveis mais rápido: componentes com comportamento ARIA real, identidade visual consistente e arquivos de teste gerados desde o primeiro comando.
5
+ `semmet-angular` é uma coleção de schematics focada em entrega para times Angular que querem publicar interfaces acessíveis mais rápido: componentes com comportamento ARIA real, compositions/blocks com estrutura validada e arquivos de teste gerados desde o primeiro comando.
6
6
 
7
- Semmet Angular ajuda times a gerar, testar, verificar e empacotar UI acessível usando o ecossistema do Angular CLI.
7
+ Semmet Angular ajuda times a gerar, compor, testar, verificar e empacotar UI acessível usando o ecossistema do Angular CLI.
8
8
 
9
- **Gerar. Testar. Verificar. Empacotar.**
9
+ **Gerar. Compor. Testar. Verificar. Empacotar.**
10
10
 
11
11
  Leia a direção geral do produto em [PRODUCT_VISION.md](./PRODUCT_VISION.md).
12
12
 
@@ -19,6 +19,7 @@ English documentation is available in [README.en.md](./README.en.md).
19
19
  ```bash
20
20
  ng add semmet-angular
21
21
  ng generate semmet-angular:accordion minha-faq
22
+ ng generate semmet-angular:block section-card
22
23
  ng generate semmet-angular:ci
23
24
  ng generate semmet-angular:docker
24
25
  ng generate semmet-angular:deploy
@@ -26,6 +27,14 @@ ng generate semmet-angular:deploy
26
27
 
27
28
  `ng add semmet-angular` instala o pacote e configura uma vez o fluxo de entrega da aplicação alvo, incluindo suporte a e2e com Playwright, scripts npm e `npm run verify`. Cada componente gerado entra no seu projeto do mesmo jeito que entraria com `ng generate component` (respeitando `angular.json`, `sourceRoot` e prefixo de seletor), e tem **zero dependência em tempo de execução** deste pacote — `semmet-angular` só é necessário na geração.
28
29
 
30
+ ## Arquitetura em Camadas
31
+
32
+ Use componentes isolados quando precisar de controle fino, ou compositions quando quiser acelerar telas acessíveis com estrutura já validada.
33
+
34
+ - **Components**: schematics isolados como `button`, `card`, `accordion`, `table` e `navbar`.
35
+ - **Compositions / Blocks**: seções prontas como `section-card`, `section-faq`, `section-pricing`, `section-stats`, `section-table`, `section-form`, `section-tabs`, `section-list-group`, `section-alert`, `section-progress`, `section-carousel`, `header-navbar`, `footer-navigation` e `command-list`.
36
+ - **Delivery**: `ng add`, `ci`, `docker`, `deploy` e `verify`.
37
+
29
38
  ## Fluxo de Entrega
30
39
 
31
40
  Depois de rodar `ng add semmet-angular`, use:
package/README.pt-BR.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # semmet-angular
2
2
 
3
- **Kit de entrega de UI acessível para Angular.**
3
+ **Dev kit de entrega de UI acessível em Angular.**
4
4
 
5
- `semmet-angular` é uma coleção de schematics focada em entrega para times Angular que querem publicar interfaces acessíveis mais rápido: componentes com comportamento ARIA real, identidade visual consistente e arquivos de teste gerados desde o primeiro comando.
5
+ `semmet-angular` é uma coleção de schematics focada em entrega para times Angular que querem publicar interfaces acessíveis mais rápido: componentes com comportamento ARIA real, compositions/blocks com estrutura validada e arquivos de teste gerados desde o primeiro comando.
6
6
 
7
- Semmet Angular ajuda times a gerar, testar, verificar e empacotar UI acessível usando o ecossistema do Angular CLI.
7
+ Semmet Angular ajuda times a gerar, compor, testar, verificar e empacotar UI acessível usando o ecossistema do Angular CLI.
8
8
 
9
- **Gerar. Testar. Verificar. Empacotar.**
9
+ **Gerar. Compor. Testar. Verificar. Empacotar.**
10
10
 
11
11
  Leia a direção geral do produto em [PRODUCT_VISION.md](./PRODUCT_VISION.md).
12
12
 
@@ -17,6 +17,7 @@ Leia a direção geral do produto em [PRODUCT_VISION.md](./PRODUCT_VISION.md).
17
17
  ```bash
18
18
  ng add semmet-angular
19
19
  ng generate semmet-angular:accordion minha-faq
20
+ ng generate semmet-angular:block section-card
20
21
  ng generate semmet-angular:ci
21
22
  ng generate semmet-angular:docker
22
23
  ng generate semmet-angular:deploy
@@ -24,6 +25,14 @@ ng generate semmet-angular:deploy
24
25
 
25
26
  `ng add semmet-angular` instala o pacote e configura uma vez o fluxo de entrega da aplicação alvo, incluindo suporte a e2e com Playwright, scripts npm e `npm run verify`. Cada componente gerado entra no seu projeto do mesmo jeito que entraria com `ng generate component` (respeitando `angular.json`, `sourceRoot` e prefixo de seletor), e tem **zero dependência em tempo de execução** deste pacote — `semmet-angular` só é necessário na geração.
26
27
 
28
+ ## Arquitetura em Camadas
29
+
30
+ Use componentes isolados quando precisar de controle fino, ou compositions quando quiser acelerar telas acessíveis com estrutura já validada.
31
+
32
+ - **Components**: schematics isolados como `button`, `card`, `accordion`, `table` e `navbar`.
33
+ - **Compositions / Blocks**: seções prontas como `section-card`, `section-faq`, `section-pricing`, `section-stats`, `section-table`, `section-form`, `section-tabs`, `section-list-group`, `section-alert`, `section-progress`, `section-carousel`, `header-navbar`, `footer-navigation` e `command-list`.
34
+ - **Delivery**: `ng add`, `ci`, `docker`, `deploy` e `verify`.
35
+
27
36
  ## Fluxo de Entrega
28
37
 
29
38
  Depois de rodar `ng add semmet-angular`, use:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "semmet-angular",
3
- "version": "0.36.0",
4
- "description": "Accessible UI Delivery Kit for Angular: schematics that generate ARIA-conformant standalone components with styling, keyboard behavior, unit tests, and Playwright e2e smoke tests.",
3
+ "version": "0.38.0",
4
+ "description": "Accessible UI delivery dev kit in Angular: schematics that generate components, compositions, tests, and delivery scaffolds as native editable Angular code.",
5
5
  "publisher": "danilodevsilva",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -5,7 +5,6 @@
5
5
  --semmet-color-on-surface: #18181b;
6
6
  --semmet-color-on-surface-variant: #52525b;
7
7
  --semmet-radius-md: 12px;
8
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
9
8
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
10
9
 
11
10
  display: block;
@@ -16,7 +15,6 @@
16
15
  background: var(--semmet-color-surface);
17
16
  border: 1px solid var(--semmet-color-outline);
18
17
  border-radius: var(--semmet-radius-md);
19
- box-shadow: var(--semmet-elevation-1);
20
18
  overflow: hidden;
21
19
  }
22
20
 
@@ -0,0 +1,103 @@
1
+ :host {
2
+ --semmet-color-primary: #18181b;
3
+ --semmet-color-on-surface: #18181b;
4
+ --semmet-color-on-surface-variant: #52525b;
5
+ --semmet-color-accent: #2563eb;
6
+
7
+ display: block;
8
+ color: var(--semmet-color-on-surface);
9
+ font-family: Roboto, system-ui, -apple-system, 'Segoe UI', sans-serif;
10
+ font-size: 1rem;
11
+ line-height: 1.5;
12
+ }
13
+
14
+ .semmet-block {
15
+ display: grid;
16
+ gap: 2rem;
17
+ max-width: 72rem;
18
+ margin-inline: auto;
19
+ margin-block: 4rem;
20
+ }
21
+
22
+ .semmet-block--hero {
23
+ min-height: 60vh;
24
+ align-content: center;
25
+ }
26
+
27
+ .semmet-block--header {
28
+ max-width: none;
29
+ margin-block: 0;
30
+ }
31
+
32
+ .semmet-block--footer {
33
+ max-width: none;
34
+ margin-block: 4rem 0;
35
+ }
36
+
37
+ .semmet-block__header {
38
+ display: grid;
39
+ gap: 0.75rem;
40
+ max-width: 44rem;
41
+ }
42
+
43
+ .semmet-block__eyebrow {
44
+ margin: 0;
45
+ color: var(--semmet-color-accent);
46
+ font-size: 0.8125rem;
47
+ font-weight: 700;
48
+ letter-spacing: 0;
49
+ text-transform: uppercase;
50
+ }
51
+
52
+ h1,
53
+ h2 {
54
+ margin: 0;
55
+ color: var(--semmet-color-primary);
56
+ font-size: 2.5rem;
57
+ line-height: 1.05;
58
+ font-weight: 700;
59
+ }
60
+
61
+ .semmet-block__summary {
62
+ margin: 0;
63
+ color: var(--semmet-color-on-surface-variant);
64
+ font-size: 1.125rem;
65
+ }
66
+
67
+ .semmet-block__grid {
68
+ display: grid;
69
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
70
+ gap: 1.25rem;
71
+ }
72
+
73
+ .semmet-block__stack {
74
+ display: grid;
75
+ gap: 0.75rem;
76
+ }
77
+
78
+ .semmet-block__stats {
79
+ display: grid;
80
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
81
+ gap: 1rem;
82
+ margin: 0;
83
+ }
84
+
85
+ .semmet-block__stats div {
86
+ display: grid;
87
+ gap: 0.5rem;
88
+ }
89
+
90
+ .semmet-block__stats dt {
91
+ color: var(--semmet-color-primary);
92
+ font-size: 2rem;
93
+ font-weight: 700;
94
+ line-height: 1.1;
95
+ }
96
+
97
+ .semmet-block__stats dd {
98
+ color: var(--semmet-color-on-surface-variant);
99
+ }
100
+
101
+ dd {
102
+ margin: 0;
103
+ }
@@ -0,0 +1,209 @@
1
+ <!-- Semmet Angular: Block / Composition - accessible section scaffold. -->
2
+ <!--
3
+ Usage from a parent component template:
4
+ Import <%= classify(name) %> in that parent component.
5
+
6
+ <<%= selector %> />
7
+ -->
8
+ <% if (kind === 'hero') { %>
9
+ <section class="semmet-block semmet-block--hero" aria-labelledby="<%= dasherize(name) %>-title">
10
+ <p>{{ eyebrow }}</p>
11
+ <h1 id="<%= dasherize(name) %>-title">{{ title }}</h1>
12
+ <p>{{ summary }}</p>
13
+ <p>
14
+ <a href="#main-content">Explore the page</a>
15
+ </p>
16
+ </section>
17
+ <% } else if (kind === 'section-card') { %>
18
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
19
+ <header class="semmet-block__header">
20
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
21
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
22
+ <p class="semmet-block__summary">{{ summary }}</p>
23
+ </header>
24
+ <div class="semmet-block__grid">
25
+ @for (item of items; track item.title) {
26
+ <<%= supportSelector %> linkLabel="">
27
+ <ng-template <%= camelize(supportName) %>Title>
28
+ {{ item.title }}
29
+ </ng-template>
30
+ <ng-template <%= camelize(supportName) %>Description>
31
+ <p>{{ item.text }}</p>
32
+ </ng-template>
33
+ </<%= supportSelector %>>
34
+ }
35
+ </div>
36
+ </section>
37
+ <% } else if (kind === 'section-faq') { %>
38
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
39
+ <header class="semmet-block__header">
40
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
41
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
42
+ <p class="semmet-block__summary">{{ summary }}</p>
43
+ </header>
44
+ <div class="semmet-block__stack">
45
+ <<%= supportSelector %>>
46
+ @for (item of items; track item.title; let first = $first) {
47
+ <ng-template <%= camelize(supportName) %>Item [id]="item.title" [title]="item.title" [expanded]="first">
48
+ <p>{{ item.text }}</p>
49
+ </ng-template>
50
+ }
51
+ </<%= supportSelector %>>
52
+ </div>
53
+ </section>
54
+ <% } else if (kind === 'section-table') { %>
55
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
56
+ <header class="semmet-block__header">
57
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
58
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
59
+ <p class="semmet-block__summary">{{ summary }}</p>
60
+ </header>
61
+ <<%= supportSelector %>>
62
+ <ng-template <%= camelize(supportName) %>Caption>
63
+ {{ title }}
64
+ </ng-template>
65
+ </<%= supportSelector %>>
66
+ </section>
67
+ <% } else if (kind === 'section-form') { %>
68
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
69
+ <header class="semmet-block__header">
70
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
71
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
72
+ <p class="semmet-block__summary">{{ summary }}</p>
73
+ </header>
74
+ <<%= supportSelector %> />
75
+ </section>
76
+ <% } else if (kind === 'section-tabs') { %>
77
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
78
+ <header class="semmet-block__header">
79
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
80
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
81
+ <p class="semmet-block__summary">{{ summary }}</p>
82
+ </header>
83
+ <<%= supportSelector %>>
84
+ @for (item of items; track item.title; let first = $first) {
85
+ <ng-template <%= camelize(supportName) %>Item [id]="item.title" [label]="item.title" [selected]="first">
86
+ <p>{{ item.text }}</p>
87
+ </ng-template>
88
+ }
89
+ </<%= supportSelector %>>
90
+ </section>
91
+ <% } else if (kind === 'section-list-group') { %>
92
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
93
+ <header class="semmet-block__header">
94
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
95
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
96
+ <p class="semmet-block__summary">{{ summary }}</p>
97
+ </header>
98
+ <<%= supportSelector %> />
99
+ </section>
100
+ <% } else if (kind === 'section-alert') { %>
101
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
102
+ <header class="semmet-block__header">
103
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
104
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
105
+ <p class="semmet-block__summary">{{ summary }}</p>
106
+ </header>
107
+ <<%= supportSelector %> />
108
+ </section>
109
+ <% } else if (kind === 'section-progress') { %>
110
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
111
+ <header class="semmet-block__header">
112
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
113
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
114
+ <p class="semmet-block__summary">{{ summary }}</p>
115
+ </header>
116
+ <<%= supportSelector %> />
117
+ </section>
118
+ <% } else if (kind === 'section-carousel') { %>
119
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
120
+ <header class="semmet-block__header">
121
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
122
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
123
+ <p class="semmet-block__summary">{{ summary }}</p>
124
+ </header>
125
+ <<%= supportSelector %>>
126
+ @for (item of items; track item.title) {
127
+ <ng-template <%= camelize(supportName) %>Slide [id]="item.title" [label]="item.title">
128
+ <h3>{{ item.title }}</h3>
129
+ <p>{{ item.text }}</p>
130
+ </ng-template>
131
+ }
132
+ </<%= supportSelector %>>
133
+ </section>
134
+ <% } else if (kind === 'header-navbar') { %>
135
+ <header class="semmet-block semmet-block--header">
136
+ <<%= supportSelector %>>
137
+ <ng-template <%= camelize(supportName) %>Brand>
138
+ {{ title }}
139
+ </ng-template>
140
+ <ng-template <%= camelize(supportName) %>Actions>
141
+ <a href="#main-content">Skip to content</a>
142
+ </ng-template>
143
+ </<%= supportSelector %>>
144
+ </header>
145
+ <% } else if (kind === 'footer-navigation') { %>
146
+ <footer class="semmet-block semmet-block--footer">
147
+ <<%= supportSelector %>>
148
+ <ng-template <%= camelize(supportName) %>Brand>
149
+ {{ title }}
150
+ </ng-template>
151
+ <ng-template <%= camelize(supportName) %>Actions>
152
+ <a href="#main-content">Back to content</a>
153
+ </ng-template>
154
+ </<%= supportSelector %>>
155
+ </footer>
156
+ <% } else if (kind === 'section-pricing') { %>
157
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
158
+ <header class="semmet-block__header">
159
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
160
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
161
+ <p class="semmet-block__summary">{{ summary }}</p>
162
+ </header>
163
+ <div class="semmet-block__grid">
164
+ @for (item of items; track item.title) {
165
+ <<%= supportSelector %>>
166
+ <ng-template <%= camelize(supportName) %>Title>
167
+ {{ item.title }}
168
+ </ng-template>
169
+ <ng-template <%= camelize(supportName) %>Description>
170
+ <p>{{ item.text }}</p>
171
+ </ng-template>
172
+ <ng-template <%= camelize(supportName) %>Actions>
173
+ <a href="/contact">Choose plan</a>
174
+ </ng-template>
175
+ </<%= supportSelector %>>
176
+ }
177
+ </div>
178
+ </section>
179
+ <% } else if (kind === 'section-stats') { %>
180
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
181
+ <header class="semmet-block__header">
182
+ <p class="semmet-block__eyebrow">{{ eyebrow }}</p>
183
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
184
+ <p class="semmet-block__summary">{{ summary }}</p>
185
+ </header>
186
+ <dl class="semmet-block__stats">
187
+ @for (item of items; track item.title) {
188
+ <div>
189
+ <dt>{{ item.title }}</dt>
190
+ <dd>{{ item.text }}</dd>
191
+ </div>
192
+ }
193
+ </dl>
194
+ </section>
195
+ <% } else { %>
196
+ <section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
197
+ <p>{{ eyebrow }}</p>
198
+ <h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
199
+ <p>{{ summary }}</p>
200
+ <ol class="semmet-block__stack">
201
+ @for (item of items; track item.title) {
202
+ <li>
203
+ <code>{{ item.title }}</code>
204
+ <p>{{ item.text }}</p>
205
+ </li>
206
+ }
207
+ </ol>
208
+ </section>
209
+ <% } %>
@@ -0,0 +1,86 @@
1
+ import { Component } from '@angular/core';
2
+ <% if (usesSupportingComponent) { %><% if (supportVariable === 'card') { %>import {
3
+ <%= classify(supportName) %>,
4
+ <% if (usesCardActions) { %>
5
+ <%= classify(supportName) %>Actions,
6
+ <% } %>
7
+ <%= classify(supportName) %>Description,
8
+ <%= classify(supportName) %>Title,
9
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
10
+ <% } else if (supportVariable === 'accordion') { %>import {
11
+ <%= classify(supportName) %>,
12
+ <%= classify(supportName) %>Item,
13
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
14
+ <% } else if (supportVariable === 'table') { %>import {
15
+ <%= classify(supportName) %>,
16
+ <%= classify(supportName) %>Caption,
17
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
18
+ <% } else if (supportVariable === 'navbar') { %>import {
19
+ <%= classify(supportName) %>,
20
+ <%= classify(supportName) %>Actions,
21
+ <%= classify(supportName) %>Brand,
22
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
23
+ <% } else if (supportVariable === 'tabs') { %>import {
24
+ <%= classify(supportName) %>,
25
+ <%= classify(supportName) %>Item,
26
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
27
+ <% } else if (supportVariable === 'carousel') { %>import {
28
+ <%= classify(supportName) %>,
29
+ <%= classify(supportName) %>Slide,
30
+ } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
31
+ <% } else { %>import { <%= classify(supportName) %> } from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
32
+ <% } %><% } %>
33
+
34
+ interface <%= classify(name) %>Item {
35
+ title: string;
36
+ text: string;
37
+ }
38
+
39
+ @Component({
40
+ selector: '<%= selector %>',
41
+ imports: [<% if (usesSupportingComponent) { %>
42
+ <%= classify(supportName) %>,
43
+ <% if (supportVariable === 'card') { %>
44
+ <% if (usesCardActions) { %>
45
+ <%= classify(supportName) %>Actions,
46
+ <% } %>
47
+ <%= classify(supportName) %>Description,
48
+ <%= classify(supportName) %>Title,
49
+ <% } else if (supportVariable === 'accordion') { %>
50
+ <%= classify(supportName) %>Item,
51
+ <% } else if (supportVariable === 'table') { %>
52
+ <%= classify(supportName) %>Caption,
53
+ <% } else if (supportVariable === 'navbar') { %>
54
+ <%= classify(supportName) %>Actions,
55
+ <%= classify(supportName) %>Brand,
56
+ <% } else if (supportVariable === 'tabs') { %>
57
+ <%= classify(supportName) %>Item,
58
+ <% } else if (supportVariable === 'carousel') { %>
59
+ <%= classify(supportName) %>Slide,
60
+ <% } %>
61
+ <% } %>],
62
+ templateUrl: './<%= dasherize(name) %>.html',
63
+ styleUrl: './<%= dasherize(name) %>.css',
64
+ })
65
+ export class <%= classify(name) %> {
66
+ protected readonly kind = '<%= kind %>';
67
+ protected readonly title = '<%= classify(kind) %>';
68
+ protected readonly eyebrow = 'Semmet composition';
69
+ protected readonly summary =
70
+ 'Accessible Angular block generated as native, editable application code.';
71
+
72
+ protected readonly items: readonly <%= classify(name) %>Item[] = [
73
+ {
74
+ title: 'Accessible structure',
75
+ text: 'Semantic regions, headings, lists, and actions start in a sensible order.',
76
+ },
77
+ {
78
+ title: 'Angular-native output',
79
+ text: 'The generated files are regular standalone component files owned by your app.',
80
+ },
81
+ {
82
+ title: 'Ready to compose',
83
+ text: 'Replace placeholder content with product components, inputs, outputs, and signals.',
84
+ },
85
+ ];
86
+ }
@@ -0,0 +1 @@
1
+ export declare const block: (options: import("../utils/generate-composition").CompositionSchematicOptions) => import("@angular-devkit/schematics").Rule;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.block = void 0;
4
+ const generate_composition_1 = require("../utils/generate-composition");
5
+ exports.block = (0, generate_composition_1.createCompositionSchematic)('hero');
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,wEAA2E;AAE9D,QAAA,KAAK,GAAG,IAAA,iDAA0B,EAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createCompositionSchematic } from '../utils/generate-composition';
2
+
3
+ export const block = createCompositionSchematic('hero');
@@ -0,0 +1,7 @@
1
+ export type BlockKind = 'hero' | 'section-card' | 'section-faq' | 'section-pricing' | 'section-stats' | 'section-table' | 'header-navbar' | 'command-list' | 'section-form' | 'section-tabs' | 'section-list-group' | 'section-alert' | 'section-progress' | 'footer-navigation' | 'section-carousel';
2
+ export interface Schema {
3
+ name: string;
4
+ project: string;
5
+ path?: string;
6
+ kind?: BlockKind;
7
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "SemmetAngularBlock",
4
+ "title": "Semmet Angular Block Options Schema",
5
+ "type": "object",
6
+ "description": "Generates an accessible Angular composition block with semantic section structure.",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "description": "The name for the new block component. Used to derive the class name, selector, and file names.",
12
+ "$default": { "$source": "argv", "index": 0 },
13
+ "x-prompt": "What name would you like to use for the block component?"
14
+ },
15
+ "kind": {
16
+ "type": "string",
17
+ "description": "The composition block pattern to generate. Defaults to the positional name when it matches a known block kind.",
18
+ "default": "hero",
19
+ "enum": [
20
+ "hero",
21
+ "section-card",
22
+ "section-faq",
23
+ "section-pricing",
24
+ "section-stats",
25
+ "section-table",
26
+ "header-navbar",
27
+ "command-list",
28
+ "section-form",
29
+ "section-tabs",
30
+ "section-list-group",
31
+ "section-alert",
32
+ "section-progress",
33
+ "footer-navigation",
34
+ "section-carousel"
35
+ ]
36
+ },
37
+ "path": {
38
+ "type": "string",
39
+ "format": "path",
40
+ "$default": { "$source": "workingDirectory" },
41
+ "description": "The path where the block files should be created, relative to the workspace root. Defaults to the project's source root.",
42
+ "visible": false
43
+ },
44
+ "project": {
45
+ "type": "string",
46
+ "description": "The name of the project to add the block to.",
47
+ "$default": { "$source": "projectName" }
48
+ }
49
+ },
50
+ "required": ["name", "project"]
51
+ }
@@ -0,0 +1,23 @@
1
+ export type BlockKind =
2
+ | 'hero'
3
+ | 'section-card'
4
+ | 'section-faq'
5
+ | 'section-pricing'
6
+ | 'section-stats'
7
+ | 'section-table'
8
+ | 'header-navbar'
9
+ | 'command-list'
10
+ | 'section-form'
11
+ | 'section-tabs'
12
+ | 'section-list-group'
13
+ | 'section-alert'
14
+ | 'section-progress'
15
+ | 'footer-navigation'
16
+ | 'section-carousel';
17
+
18
+ export interface Schema {
19
+ name: string;
20
+ project: string;
21
+ path?: string;
22
+ kind?: BlockKind;
23
+ }
@@ -8,7 +8,6 @@
8
8
  --semmet-color-on-surface: #18181b;
9
9
  --semmet-color-on-surface-variant: #52525b;
10
10
  --semmet-radius-sm: 8px;
11
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
12
11
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
13
12
 
14
13
  display: inline-block;
@@ -31,12 +30,10 @@
31
30
  white-space: nowrap;
32
31
  cursor: pointer;
33
32
  user-select: none;
34
- box-shadow: var(--semmet-elevation-1);
35
33
  transition:
36
34
  background-color var(--semmet-motion),
37
35
  border-color var(--semmet-motion),
38
36
  color var(--semmet-motion),
39
- box-shadow var(--semmet-motion),
40
37
  transform var(--semmet-motion);
41
38
  }
42
39
 
@@ -77,7 +74,6 @@
77
74
  color: var(--semmet-color-on-surface);
78
75
  background: transparent;
79
76
  border-color: transparent;
80
- box-shadow: none;
81
77
  }
82
78
 
83
79
  .semmet-button:hover:not(:disabled) {
@@ -91,7 +87,6 @@
91
87
  .semmet-button:disabled {
92
88
  cursor: not-allowed;
93
89
  opacity: 0.56;
94
- box-shadow: none;
95
90
  }
96
91
 
97
92
  .semmet-button:focus-visible {
@@ -5,7 +5,6 @@
5
5
  --semmet-color-on-surface: #18181b;
6
6
  --semmet-color-on-surface-variant: #52525b;
7
7
  --semmet-radius-md: 12px;
8
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
9
8
 
10
9
  display: block;
11
10
  font-family: Roboto, system-ui, -apple-system, 'Segoe UI', sans-serif;
@@ -17,7 +16,6 @@ article {
17
16
  background: var(--semmet-color-surface);
18
17
  border: 1px solid var(--semmet-color-outline);
19
18
  border-radius: var(--semmet-radius-md);
20
- box-shadow: var(--semmet-elevation-1);
21
19
  }
22
20
 
23
21
  h3 {
@@ -5,7 +5,6 @@
5
5
  --semmet-color-outline: #d4d4d8;
6
6
  --semmet-color-on-surface: #18181b;
7
7
  --semmet-radius-md: 12px;
8
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
9
8
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
10
9
 
11
10
  display: block;
@@ -31,7 +30,6 @@ section {
31
30
  background: var(--semmet-color-surface);
32
31
  border: 1px solid var(--semmet-color-outline);
33
32
  border-radius: var(--semmet-radius-md);
34
- box-shadow: var(--semmet-elevation-1);
35
33
  text-align: center;
36
34
  }
37
35
 
@@ -21,6 +21,11 @@
21
21
  "factory": "./deploy/index#deploy",
22
22
  "schema": "./deploy/schema.json"
23
23
  },
24
+ "block": {
25
+ "description": "Generates an accessible Angular composition block with semantic section structure.",
26
+ "factory": "./block/index#block",
27
+ "schema": "./block/schema.json"
28
+ },
24
29
  "accordion": {
25
30
  "description": "Generates a standalone Angular Accordion component following the W3C ARIA Authoring Practices Guide.",
26
31
  "factory": "./accordion/index#accordion",
@@ -5,7 +5,6 @@
5
5
  --semmet-color-on-surface: #18181b;
6
6
  --semmet-color-on-surface-variant: #52525b;
7
7
  --semmet-radius-md: 12px;
8
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
9
8
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
10
9
 
11
10
  display: block;
@@ -16,7 +15,6 @@
16
15
  background: var(--semmet-color-surface);
17
16
  border: 1px solid var(--semmet-color-outline);
18
17
  border-radius: var(--semmet-radius-md);
19
- box-shadow: var(--semmet-elevation-1);
20
18
  overflow: hidden;
21
19
  }
22
20
 
@@ -8,7 +8,6 @@
8
8
  --semmet-color-on-surface-variant: #52525b;
9
9
  --semmet-radius-sm: 8px;
10
10
  --semmet-radius-md: 12px;
11
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
12
11
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
13
12
 
14
13
  display: block;
@@ -25,7 +24,6 @@ form {
25
24
  background: var(--semmet-color-surface);
26
25
  border: 1px solid var(--semmet-color-outline);
27
26
  border-radius: var(--semmet-radius-md);
28
- box-shadow: var(--semmet-elevation-1);
29
27
  }
30
28
 
31
29
  h2 {
@@ -57,9 +55,7 @@ textarea {
57
55
  font: inherit;
58
56
  color: var(--semmet-color-on-surface);
59
57
  background: var(--semmet-color-surface);
60
- transition:
61
- border-color var(--semmet-motion),
62
- box-shadow var(--semmet-motion);
58
+ transition: border-color var(--semmet-motion);
63
59
  }
64
60
 
65
61
  input {
@@ -7,7 +7,6 @@
7
7
  --semmet-color-on-surface: #27272a;
8
8
  --semmet-color-on-surface-variant: #71717a;
9
9
  --semmet-radius-md: 10px;
10
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
11
10
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
12
11
 
13
12
  display: block;
@@ -23,13 +22,11 @@
23
22
  border: 1px solid var(--semmet-color-outline);
24
23
  border-radius: var(--semmet-radius-md);
25
24
  background: var(--semmet-color-surface);
26
- box-shadow: var(--semmet-elevation-1);
27
25
  list-style: none;
28
26
  }
29
27
 
30
28
  .semmet-list-group--flush {
31
29
  border-radius: 0;
32
- box-shadow: none;
33
30
  }
34
31
 
35
32
  .semmet-list-group__item + .semmet-list-group__item {
@@ -6,7 +6,6 @@
6
6
  --semmet-color-on-surface: #27272a;
7
7
  --semmet-color-on-surface-variant: #71717a;
8
8
  --semmet-radius-md: 10px;
9
- --semmet-elevation-1: 0 1px 2px rgba(20, 24, 23, 0.1), 0 1px 1px rgba(20, 24, 23, 0.06);
10
9
  --semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
11
10
 
12
11
  display: block;
@@ -23,7 +22,6 @@
23
22
  border-radius: var(--semmet-radius-md);
24
23
  color: var(--semmet-color-on-surface);
25
24
  background: var(--semmet-color-surface);
26
- box-shadow: var(--semmet-elevation-1);
27
25
  }
28
26
 
29
27
  .semmet-navbar__brand {
@@ -1,4 +1,5 @@
1
1
  export interface Schema {
2
2
  project?: string;
3
+ saveDev?: boolean;
3
4
  skipE2e?: boolean;
4
5
  }
@@ -15,6 +15,11 @@
15
15
  "type": "boolean",
16
16
  "default": false,
17
17
  "description": "Skip Playwright e2e setup."
18
+ },
19
+ "saveDev": {
20
+ "type": "boolean",
21
+ "default": false,
22
+ "description": "Accepted from Angular CLI when ng add is invoked with --save-dev."
18
23
  }
19
24
  }
20
25
  }
@@ -1,4 +1,5 @@
1
1
  export interface Schema {
2
2
  project?: string;
3
+ saveDev?: boolean;
3
4
  skipE2e?: boolean;
4
5
  }
@@ -0,0 +1,8 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ export interface CompositionSchematicOptions {
3
+ name: string;
4
+ project: string;
5
+ path?: string;
6
+ kind?: string;
7
+ }
8
+ export declare function createCompositionSchematic(defaultKind: string): (options: CompositionSchematicOptions) => Rule;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCompositionSchematic = createCompositionSchematic;
4
+ const core_1 = require("@angular-devkit/core");
5
+ const schematics_1 = require("@angular-devkit/schematics");
6
+ const project_1 = require("@schematics/angular/utility/project");
7
+ const workspace_1 = require("@schematics/angular/utility/workspace");
8
+ const parse_name_1 = require("@schematics/angular/utility/parse-name");
9
+ const validation_1 = require("@schematics/angular/utility/validation");
10
+ const knownKinds = new Set([
11
+ 'command-list',
12
+ 'footer-navigation',
13
+ 'header-navbar',
14
+ 'hero',
15
+ 'section-alert',
16
+ 'section-carousel',
17
+ 'section-card',
18
+ 'section-faq',
19
+ 'section-form',
20
+ 'section-list-group',
21
+ 'section-pricing',
22
+ 'section-progress',
23
+ 'section-stats',
24
+ 'section-table',
25
+ 'section-tabs',
26
+ ]);
27
+ function getSupportingComponent(kind) {
28
+ switch (kind) {
29
+ case 'section-card':
30
+ case 'section-pricing':
31
+ return { templatePath: '../card/files', variable: 'card' };
32
+ case 'section-faq':
33
+ return { templatePath: '../accordion/files', variable: 'accordion' };
34
+ case 'section-table':
35
+ return { templatePath: '../table/files', variable: 'table' };
36
+ case 'header-navbar':
37
+ case 'footer-navigation':
38
+ return { templatePath: '../navbar/files', variable: 'navbar' };
39
+ case 'section-form':
40
+ return { templatePath: '../form/files', variable: 'form' };
41
+ case 'section-tabs':
42
+ return { templatePath: '../tabs/files', variable: 'tabs' };
43
+ case 'section-list-group':
44
+ return { templatePath: '../list-group/files', variable: 'listGroup' };
45
+ case 'section-alert':
46
+ return { templatePath: '../alert/files', variable: 'alert' };
47
+ case 'section-progress':
48
+ return { templatePath: '../progress-bar/files', variable: 'progressBar' };
49
+ case 'section-carousel':
50
+ return { templatePath: '../carousel/files', variable: 'carousel' };
51
+ default:
52
+ return null;
53
+ }
54
+ }
55
+ function createCompositionSchematic(defaultKind) {
56
+ return (0, project_1.createProjectSchematic)((options, { project }) => {
57
+ options.path ??= (0, workspace_1.buildDefaultPath)(project);
58
+ const parsedPath = (0, parse_name_1.parseName)(options.path, options.name);
59
+ const name = parsedPath.name;
60
+ const kind = knownKinds.has(name) && (options.kind === undefined || options.kind === defaultKind)
61
+ ? name
62
+ : options.kind ?? defaultKind;
63
+ const selector = `${project.prefix ? `${project.prefix}-` : ''}${core_1.strings.dasherize(name)}`;
64
+ const supportingComponent = getSupportingComponent(kind);
65
+ const supportName = supportingComponent ? `${name}-${supportingComponent.variable}` : '';
66
+ const supportSelector = supportName
67
+ ? `${project.prefix ? `${project.prefix}-` : ''}${core_1.strings.dasherize(supportName)}`
68
+ : '';
69
+ const usesCardActions = kind === 'section-pricing';
70
+ (0, validation_1.validateHtmlSelector)(selector);
71
+ (0, validation_1.validateClassName)(core_1.strings.classify(name));
72
+ if (supportingComponent) {
73
+ (0, validation_1.validateHtmlSelector)(supportSelector);
74
+ (0, validation_1.validateClassName)(core_1.strings.classify(supportName));
75
+ }
76
+ const templateSource = (0, schematics_1.apply)((0, schematics_1.url)('./files'), [
77
+ (0, schematics_1.applyTemplates)({
78
+ ...core_1.strings,
79
+ name,
80
+ selector,
81
+ kind,
82
+ supportName,
83
+ supportSelector,
84
+ supportVariable: supportingComponent?.variable ?? '',
85
+ usesSupportingComponent: Boolean(supportingComponent),
86
+ usesCardActions,
87
+ }),
88
+ (0, schematics_1.move)(parsedPath.path),
89
+ ]);
90
+ const rules = [(0, schematics_1.mergeWith)(templateSource)];
91
+ if (supportingComponent) {
92
+ const supportSource = (0, schematics_1.apply)((0, schematics_1.url)(supportingComponent.templatePath), [
93
+ (0, schematics_1.filter)((filePath) => !filePath.endsWith('.spec.ts.template') && !filePath.endsWith('.e2e-spec.ts.template')),
94
+ (0, schematics_1.applyTemplates)({ ...core_1.strings, name: supportName, selector: supportSelector }),
95
+ (0, schematics_1.move)(`${parsedPath.path}/${core_1.strings.dasherize(name)}`),
96
+ ]);
97
+ rules.push((0, schematics_1.mergeWith)(supportSource));
98
+ }
99
+ return (0, schematics_1.chain)(rules);
100
+ });
101
+ }
102
+ //# sourceMappingURL=generate-composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-composition.js","sourceRoot":"","sources":["generate-composition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import { strings } from '@angular-devkit/core';
2
+ import { Rule, apply, applyTemplates, chain, filter, mergeWith, move, url } from '@angular-devkit/schematics';
3
+ import { createProjectSchematic } from '@schematics/angular/utility/project';
4
+ import { buildDefaultPath } from '@schematics/angular/utility/workspace';
5
+ import { parseName } from '@schematics/angular/utility/parse-name';
6
+ import { validateClassName, validateHtmlSelector } from '@schematics/angular/utility/validation';
7
+
8
+ export interface CompositionSchematicOptions {
9
+ name: string;
10
+ project: string;
11
+ path?: string;
12
+ kind?: string;
13
+ }
14
+
15
+ const knownKinds = new Set([
16
+ 'command-list',
17
+ 'footer-navigation',
18
+ 'header-navbar',
19
+ 'hero',
20
+ 'section-alert',
21
+ 'section-carousel',
22
+ 'section-card',
23
+ 'section-faq',
24
+ 'section-form',
25
+ 'section-list-group',
26
+ 'section-pricing',
27
+ 'section-progress',
28
+ 'section-stats',
29
+ 'section-table',
30
+ 'section-tabs',
31
+ ]);
32
+
33
+ interface SupportingComponent {
34
+ templatePath: string;
35
+ variable: string;
36
+ }
37
+
38
+ function getSupportingComponent(kind: string): SupportingComponent | null {
39
+ switch (kind) {
40
+ case 'section-card':
41
+ case 'section-pricing':
42
+ return { templatePath: '../card/files', variable: 'card' };
43
+ case 'section-faq':
44
+ return { templatePath: '../accordion/files', variable: 'accordion' };
45
+ case 'section-table':
46
+ return { templatePath: '../table/files', variable: 'table' };
47
+ case 'header-navbar':
48
+ case 'footer-navigation':
49
+ return { templatePath: '../navbar/files', variable: 'navbar' };
50
+ case 'section-form':
51
+ return { templatePath: '../form/files', variable: 'form' };
52
+ case 'section-tabs':
53
+ return { templatePath: '../tabs/files', variable: 'tabs' };
54
+ case 'section-list-group':
55
+ return { templatePath: '../list-group/files', variable: 'listGroup' };
56
+ case 'section-alert':
57
+ return { templatePath: '../alert/files', variable: 'alert' };
58
+ case 'section-progress':
59
+ return { templatePath: '../progress-bar/files', variable: 'progressBar' };
60
+ case 'section-carousel':
61
+ return { templatePath: '../carousel/files', variable: 'carousel' };
62
+ default:
63
+ return null;
64
+ }
65
+ }
66
+
67
+ export function createCompositionSchematic(defaultKind: string): (options: CompositionSchematicOptions) => Rule {
68
+ return createProjectSchematic<CompositionSchematicOptions>((options, { project }) => {
69
+ options.path ??= buildDefaultPath(project);
70
+
71
+ const parsedPath = parseName(options.path, options.name);
72
+ const name = parsedPath.name;
73
+ const kind = knownKinds.has(name) && (options.kind === undefined || options.kind === defaultKind)
74
+ ? name
75
+ : options.kind ?? defaultKind;
76
+ const selector = `${project.prefix ? `${project.prefix}-` : ''}${strings.dasherize(name)}`;
77
+ const supportingComponent = getSupportingComponent(kind);
78
+ const supportName = supportingComponent ? `${name}-${supportingComponent.variable}` : '';
79
+ const supportSelector = supportName
80
+ ? `${project.prefix ? `${project.prefix}-` : ''}${strings.dasherize(supportName)}`
81
+ : '';
82
+ const usesCardActions = kind === 'section-pricing';
83
+
84
+ validateHtmlSelector(selector);
85
+ validateClassName(strings.classify(name));
86
+
87
+ if (supportingComponent) {
88
+ validateHtmlSelector(supportSelector);
89
+ validateClassName(strings.classify(supportName));
90
+ }
91
+
92
+ const templateSource = apply(url('./files'), [
93
+ applyTemplates({
94
+ ...strings,
95
+ name,
96
+ selector,
97
+ kind,
98
+ supportName,
99
+ supportSelector,
100
+ supportVariable: supportingComponent?.variable ?? '',
101
+ usesSupportingComponent: Boolean(supportingComponent),
102
+ usesCardActions,
103
+ }),
104
+ move(parsedPath.path),
105
+ ]);
106
+
107
+ const rules: Rule[] = [mergeWith(templateSource)];
108
+
109
+ if (supportingComponent) {
110
+ const supportSource = apply(url(supportingComponent.templatePath), [
111
+ filter((filePath) => !filePath.endsWith('.spec.ts.template') && !filePath.endsWith('.e2e-spec.ts.template')),
112
+ applyTemplates({ ...strings, name: supportName, selector: supportSelector }),
113
+ move(`${parsedPath.path}/${strings.dasherize(name)}`),
114
+ ]);
115
+
116
+ rules.push(mergeWith(supportSource));
117
+ }
118
+
119
+ return chain(rules);
120
+ });
121
+ }