semmet-angular 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/accordion/files/__name@dasherize__/__name@dasherize__.css.template +0 -39
- package/src/accordion/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +16 -17
- package/src/accordion/files/__name@dasherize__/__name@dasherize__.html.template +14 -34
- package/src/accordion/files/__name@dasherize__/__name@dasherize__.spec.ts.template +52 -62
- package/src/accordion/files/__name@dasherize__/__name@dasherize__.ts.template +88 -82
- package/src/accordion/schema.json +1 -1
- package/src/block/files/__name@dasherize__/__name@dasherize__.html.template +51 -23
- package/src/block/files/__name@dasherize__/__name@dasherize__.ts.template +16 -10
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.css.template +0 -32
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +5 -2
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.html.template +8 -7
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.spec.ts.template +51 -0
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.ts.template +18 -19
- package/src/breadcrumb/index.js +1 -1
- package/src/breadcrumb/index.js.map +1 -1
- package/src/breadcrumb/index.ts +1 -1
- package/src/breadcrumb/schema.json +1 -1
- package/src/collection.json +9 -9
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.css.template +0 -48
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.html.template +14 -35
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.spec.ts.template +53 -11
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.ts.template +128 -54
- package/src/menu-button/schema.json +1 -1
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.css.template +0 -119
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +10 -7
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.html.template +18 -42
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.spec.ts.template +46 -23
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.ts.template +54 -50
- package/src/navbar/schema.json +1 -1
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.css.template +0 -35
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.html.template +11 -13
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.spec.ts.template +58 -8
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.ts.template +17 -31
- package/src/pagination/schema.json +1 -1
- package/src/table/files/__name@dasherize__/__name@dasherize__.css.template +0 -17
- package/src/table/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +1 -1
- package/src/table/files/__name@dasherize__/__name@dasherize__.html.template +13 -25
- package/src/table/files/__name@dasherize__/__name@dasherize__.ts.template +3 -35
- package/src/table/schema.json +1 -1
- package/src/tabs/files/__name@dasherize__/__name@dasherize__.css.template +0 -40
- package/src/tabs/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
- package/src/tabs/files/__name@dasherize__/__name@dasherize__.html.template +18 -41
- package/src/tabs/files/__name@dasherize__/__name@dasherize__.spec.ts.template +39 -31
- package/src/tabs/files/__name@dasherize__/__name@dasherize__.ts.template +111 -113
- package/src/tabs/schema.json +1 -1
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.css.template +0 -19
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +3 -4
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.html.template +7 -16
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.spec.ts.template +26 -11
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.ts.template +59 -37
- package/src/toolbar/schema.json +1 -1
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.css.template +0 -39
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +4 -5
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.html.template +12 -31
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.spec.ts.template +80 -43
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.ts.template +105 -88
- package/src/tree-view/schema.json +1 -1
|
@@ -38,14 +38,15 @@ Import <%= classify(name) %> in that parent component.
|
|
|
38
38
|
<h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
|
|
39
39
|
<p class="semmet-block__summary">{{ summary }}</p>
|
|
40
40
|
</header>
|
|
41
|
-
<div class="semmet-block__stack"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
<div class="semmet-block__stack" <%= camelize(supportName) %>>
|
|
42
|
+
@for (item of items; track item.title; let first = $first) {
|
|
43
|
+
<h3>
|
|
44
|
+
<button type="button" <%= camelize(supportName) %>Header [value]="item.title" [expanded]="first">{{ item.title }}</button>
|
|
45
|
+
</h3>
|
|
46
|
+
<div <%= camelize(supportName) %>Panel [value]="item.title">
|
|
47
|
+
<p>{{ item.text }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
}
|
|
49
50
|
</div>
|
|
50
51
|
</section>
|
|
51
52
|
<% } else if (kind === 'section-table') { %>
|
|
@@ -56,9 +57,21 @@ Import <%= classify(name) %> in that parent component.
|
|
|
56
57
|
<p class="semmet-block__summary">{{ summary }}</p>
|
|
57
58
|
</header>
|
|
58
59
|
<<%= supportSelector %>>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
<caption>{{ title }}</caption>
|
|
61
|
+
<thead>
|
|
62
|
+
<tr>
|
|
63
|
+
<th scope="col">Item</th>
|
|
64
|
+
<th scope="col">Details</th>
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody>
|
|
68
|
+
@for (item of items; track item.title) {
|
|
69
|
+
<tr>
|
|
70
|
+
<th scope="row">{{ item.title }}</th>
|
|
71
|
+
<td>{{ item.text }}</td>
|
|
72
|
+
</tr>
|
|
73
|
+
}
|
|
74
|
+
</tbody>
|
|
62
75
|
</<%= supportSelector %>>
|
|
63
76
|
</section>
|
|
64
77
|
<% } else if (kind === 'section-form') { %>
|
|
@@ -77,13 +90,18 @@ Import <%= classify(name) %> in that parent component.
|
|
|
77
90
|
<h2 id="<%= dasherize(name) %>-title">{{ title }}</h2>
|
|
78
91
|
<p class="semmet-block__summary">{{ summary }}</p>
|
|
79
92
|
</header>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
<div <%= camelize(supportName) %>>
|
|
94
|
+
<div <%= camelize(supportName) %>List [label]="title">
|
|
95
|
+
@for (item of items; track item.title; let first = $first) {
|
|
96
|
+
<button type="button" <%= camelize(supportName) %>Tab [value]="item.title" [selected]="first">{{ item.title }}</button>
|
|
97
|
+
}
|
|
98
|
+
</div>
|
|
99
|
+
@for (item of items; track item.title) {
|
|
100
|
+
<div <%= camelize(supportName) %>Panel [value]="item.title">
|
|
83
101
|
<p>{{ item.text }}</p>
|
|
84
|
-
</
|
|
102
|
+
</div>
|
|
85
103
|
}
|
|
86
|
-
|
|
104
|
+
</div>
|
|
87
105
|
</section>
|
|
88
106
|
<% } else if (kind === 'section-list-group') { %>
|
|
89
107
|
<section class="semmet-block" aria-labelledby="<%= dasherize(name) %>-title">
|
|
@@ -137,14 +155,24 @@ Import <%= classify(name) %> in that parent component.
|
|
|
137
155
|
</section>
|
|
138
156
|
<% } else if (kind === 'header-navbar') { %>
|
|
139
157
|
<header class="semmet-block semmet-block--header">
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
<nav <%= camelize(supportName) %> label="Main navigation" #<%= camelize(supportName) %>Ref="<%= camelize(supportName) %>">
|
|
159
|
+
<a class="semmet-block__brand" href="/">{{ title }}</a>
|
|
160
|
+
|
|
161
|
+
<button type="button" <%= camelize(supportName) %>Toggle aria-label="Toggle navigation">
|
|
162
|
+
<span aria-hidden="true"></span>
|
|
163
|
+
</button>
|
|
164
|
+
|
|
165
|
+
<div <%= camelize(supportName) %>Menu>
|
|
166
|
+
<ul>
|
|
167
|
+
@for (item of items; track item.title) {
|
|
168
|
+
<li>
|
|
169
|
+
<a <%= camelize(supportName) %>Item href="#<%= dasherize(name) %>-{{ item.title }}">{{ item.title }}</a>
|
|
170
|
+
</li>
|
|
171
|
+
}
|
|
172
|
+
</ul>
|
|
145
173
|
<a href="#main-content">Skip to content</a>
|
|
146
|
-
</
|
|
147
|
-
|
|
174
|
+
</div>
|
|
175
|
+
</nav>
|
|
148
176
|
</header>
|
|
149
177
|
<% } else if (kind === 'footer-navigation') { %>
|
|
150
178
|
<footer class="semmet-block semmet-block--footer">
|
|
@@ -4,20 +4,23 @@ import { Component } from '@angular/core';
|
|
|
4
4
|
} from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
|
|
5
5
|
<% } else if (supportVariable === 'accordion') { %>import {
|
|
6
6
|
<%= classify(supportName) %>,
|
|
7
|
-
<%= classify(supportName) %>
|
|
7
|
+
<%= classify(supportName) %>Header,
|
|
8
|
+
<%= classify(supportName) %>Panel,
|
|
8
9
|
} from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
|
|
9
10
|
<% } else if (supportVariable === 'table') { %>import {
|
|
10
11
|
<%= classify(supportName) %>,
|
|
11
|
-
<%= classify(supportName) %>Caption,
|
|
12
12
|
} from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
|
|
13
13
|
<% } else if (supportVariable === 'navbar') { %>import {
|
|
14
14
|
<%= classify(supportName) %>,
|
|
15
|
-
<%= classify(supportName) %>
|
|
16
|
-
<%= classify(supportName) %>
|
|
15
|
+
<%= classify(supportName) %>Item,
|
|
16
|
+
<%= classify(supportName) %>Menu,
|
|
17
|
+
<%= classify(supportName) %>Toggle,
|
|
17
18
|
} from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
|
|
18
19
|
<% } else if (supportVariable === 'tabs') { %>import {
|
|
19
20
|
<%= classify(supportName) %>,
|
|
20
|
-
<%= classify(supportName) %>
|
|
21
|
+
<%= classify(supportName) %>List,
|
|
22
|
+
<%= classify(supportName) %>Panel,
|
|
23
|
+
<%= classify(supportName) %>Tab,
|
|
21
24
|
} from './<%= dasherize(supportName) %>/<%= dasherize(supportName) %>';
|
|
22
25
|
<% } else if (supportVariable === 'carousel') { %>import {
|
|
23
26
|
<%= classify(supportName) %>,
|
|
@@ -49,14 +52,17 @@ interface <%= classify(name) %>FooterGroup {
|
|
|
49
52
|
<%= classify(supportName) %>,
|
|
50
53
|
<% if (supportVariable === 'card') { %>
|
|
51
54
|
<% } else if (supportVariable === 'accordion') { %>
|
|
52
|
-
<%= classify(supportName) %>
|
|
55
|
+
<%= classify(supportName) %>Header,
|
|
56
|
+
<%= classify(supportName) %>Panel,
|
|
53
57
|
<% } else if (supportVariable === 'table') { %>
|
|
54
|
-
<%= classify(supportName) %>Caption,
|
|
55
58
|
<% } else if (supportVariable === 'navbar') { %>
|
|
56
|
-
<%= classify(supportName) %>Actions,
|
|
57
|
-
<%= classify(supportName) %>Brand,
|
|
58
|
-
<% } else if (supportVariable === 'tabs') { %>
|
|
59
59
|
<%= classify(supportName) %>Item,
|
|
60
|
+
<%= classify(supportName) %>Menu,
|
|
61
|
+
<%= classify(supportName) %>Toggle,
|
|
62
|
+
<% } else if (supportVariable === 'tabs') { %>
|
|
63
|
+
<%= classify(supportName) %>List,
|
|
64
|
+
<%= classify(supportName) %>Panel,
|
|
65
|
+
<%= classify(supportName) %>Tab,
|
|
60
66
|
<% } else if (supportVariable === 'carousel') { %>
|
|
61
67
|
<%= classify(supportName) %>Slide,
|
|
62
68
|
<% } else if (supportVariable === 'listGroup') { %>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: block;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
ol {
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-wrap: wrap;
|
|
8
|
-
align-items: center;
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
list-style: none;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
li {
|
|
15
|
-
display: flex;
|
|
16
|
-
align-items: center;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
li:not(:last-child)::after {
|
|
20
|
-
content: '/';
|
|
21
|
-
opacity: 0.6;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
a {
|
|
25
|
-
color: inherit;
|
|
26
|
-
text-decoration: none;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
a:focus-visible {
|
|
30
|
-
outline: 2px solid currentColor;
|
|
31
|
-
outline-offset: 2px;
|
|
32
|
-
}
|
|
@@ -3,8 +3,11 @@ import { expect, test } from '@playwright/test';
|
|
|
3
3
|
test.describe('<%= classify(name) %>', () => {
|
|
4
4
|
test('renders mounted breadcrumb navigation with current page', async ({ page }) => {
|
|
5
5
|
await page.goto('/');
|
|
6
|
-
const component = page.locator('<%=
|
|
7
|
-
test.skip(
|
|
6
|
+
const component = page.locator('[<%= camelize(name) %>]').first();
|
|
7
|
+
test.skip(
|
|
8
|
+
(await component.count()) === 0,
|
|
9
|
+
'Mount an element with [<%= camelize(name) %>] in a route or host component to enable this generated e2e test.'
|
|
10
|
+
);
|
|
8
11
|
|
|
9
12
|
await expect(component.getByRole('navigation', { name: /breadcrumb/i })).toBeVisible();
|
|
10
13
|
await expect(component.locator('[aria-current="page"]')).toBeVisible();
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
<!-- Semmet Angular: Breadcrumb — https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/ -->
|
|
2
2
|
<!--
|
|
3
3
|
Usage from a parent component template:
|
|
4
|
-
Import <%= classify(name) %> in that parent component.
|
|
4
|
+
Import <%= classify(name) %> and <%= classify(name) %>Item in that parent component.
|
|
5
|
+
This directive set never owns markup: bring your own <nav>/<ol>/<li> structure.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
-->
|
|
8
|
-
<nav [attr.aria-label]="label()">
|
|
7
|
+
<nav <%= camelize(name) %>>
|
|
9
8
|
<ol>
|
|
10
|
-
@for (item of
|
|
11
|
-
<li
|
|
9
|
+
@for (item of trail; track item.href) {
|
|
10
|
+
<li>
|
|
11
|
+
<a <%= camelize(name) %>Item [href]="item.href" [current]="item.current">{{ item.label }}</a>
|
|
12
|
+
</li>
|
|
12
13
|
}
|
|
13
|
-
<li aria-current="page">{{ currentPageLabel() }}</li>
|
|
14
14
|
</ol>
|
|
15
15
|
</nav>
|
|
16
|
+
-->
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
|
|
4
|
+
import { <%= classify(name) %>, <%= classify(name) %>Item } from './<%= dasherize(name) %>';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
imports: [<%= classify(name) %>, <%= classify(name) %>Item],
|
|
8
|
+
template: `
|
|
9
|
+
<nav <%= camelize(name) %>>
|
|
10
|
+
<ol>
|
|
11
|
+
@for (item of trail; track item.href) {
|
|
12
|
+
<li>
|
|
13
|
+
<a <%= camelize(name) %>Item [href]="item.href" [current]="item.current">{{ item.label }}</a>
|
|
14
|
+
</li>
|
|
15
|
+
}
|
|
16
|
+
</ol>
|
|
17
|
+
</nav>
|
|
18
|
+
`,
|
|
19
|
+
})
|
|
20
|
+
class HostComponent {
|
|
21
|
+
readonly trail = [
|
|
22
|
+
{ label: 'Home', href: '/', current: false },
|
|
23
|
+
{ label: 'Category', href: '/category', current: false },
|
|
24
|
+
{ label: 'Current item', href: '/category/current-item', current: true },
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('<%= classify(name) %>', () => {
|
|
29
|
+
let fixture: ComponentFixture<HostComponent>;
|
|
30
|
+
let host: HTMLElement;
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await TestBed.configureTestingModule({
|
|
34
|
+
imports: [HostComponent],
|
|
35
|
+
}).compileComponents();
|
|
36
|
+
|
|
37
|
+
fixture = TestBed.createComponent(HostComponent);
|
|
38
|
+
fixture.detectChanges();
|
|
39
|
+
host = fixture.nativeElement as HTMLElement;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('decorates consumer-owned markup with breadcrumb navigation semantics', () => {
|
|
43
|
+
const nav = host.querySelector<HTMLElement>('nav');
|
|
44
|
+
const links = Array.from(host.querySelectorAll<HTMLAnchorElement>('a'));
|
|
45
|
+
|
|
46
|
+
expect(nav?.getAttribute('aria-label')).toBe('Breadcrumb');
|
|
47
|
+
expect(links.length).toBe(3);
|
|
48
|
+
expect(links[0].getAttribute('aria-current')).toBeNull();
|
|
49
|
+
expect(links[2].getAttribute('aria-current')).toBe('page');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Directive, booleanAttribute, input } from '@angular/core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const DEFAULT_ITEMS: readonly <%= classify(name) %>Item[] = [
|
|
10
|
-
{ id: 1, label: 'Home', href: '/' },
|
|
11
|
-
{ id: 2, label: 'Category', href: '/category' },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
@Component({
|
|
15
|
-
selector: '<%= selector %>',
|
|
16
|
-
imports: [],
|
|
17
|
-
templateUrl: './<%= dasherize(name) %>.html',
|
|
18
|
-
styleUrl: './<%= dasherize(name) %>.css',
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[<%= camelize(name) %>]',
|
|
5
|
+
exportAs: '<%= camelize(name) %>',
|
|
6
|
+
host: {
|
|
7
|
+
'[attr.aria-label]': 'label() || null',
|
|
8
|
+
},
|
|
19
9
|
})
|
|
20
10
|
export class <%= classify(name) %> {
|
|
21
11
|
readonly label = input('Breadcrumb');
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Directive({
|
|
15
|
+
selector: '[<%= camelize(name) %>Item]',
|
|
16
|
+
exportAs: '<%= camelize(name) %>Item',
|
|
17
|
+
host: {
|
|
18
|
+
'[attr.aria-current]': 'current() ? "page" : null',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
export class <%= classify(name) %>Item {
|
|
22
|
+
readonly current = input(false, { transform: booleanAttribute });
|
|
24
23
|
}
|
package/src/breadcrumb/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.breadcrumb = void 0;
|
|
4
4
|
const generate_component_1 = require("../utils/generate-component");
|
|
5
|
-
exports.breadcrumb = (0, generate_component_1.createAriaComponentSchematic)({ includeSharedE2eTest: false });
|
|
5
|
+
exports.breadcrumb = (0, generate_component_1.createAriaComponentSchematic)({ includeSharedUnitTest: false, includeSharedE2eTest: false });
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,oEAA2E;AAE9D,QAAA,UAAU,GAAG,IAAA,iDAA4B,EAAC,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,oEAA2E;AAE9D,QAAA,UAAU,GAAG,IAAA,iDAA4B,EAAC,EAAE,qBAAqB,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,CAAC"}
|
package/src/breadcrumb/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { createAriaComponentSchematic } from '../utils/generate-component';
|
|
2
2
|
|
|
3
|
-
export const breadcrumb = createAriaComponentSchematic({ includeSharedE2eTest: false });
|
|
3
|
+
export const breadcrumb = createAriaComponentSchematic({ includeSharedUnitTest: false, includeSharedE2eTest: false });
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "SemmetAngularBreadcrumb",
|
|
4
4
|
"title": "Semmet Angular Breadcrumb Options Schema",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"description": "Generates
|
|
6
|
+
"description": "Generates headless Angular Breadcrumb directives (root, item) coordinated over consumer-owned nav/ol/li markup, following the W3C ARIA Authoring Practices Guide.",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|
package/src/collection.json
CHANGED
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"schema": "./block/schema.json"
|
|
33
33
|
},
|
|
34
34
|
"accordion": {
|
|
35
|
-
"description": "Generates
|
|
35
|
+
"description": "Generates headless Angular Accordion directives (root, header, panel) coordinated over consumer-owned markup, following the W3C ARIA Authoring Practices Guide.",
|
|
36
36
|
"factory": "./accordion/index#accordion",
|
|
37
37
|
"schema": "./accordion/schema.json"
|
|
38
38
|
},
|
|
39
39
|
"tabs": {
|
|
40
|
-
"description": "Generates
|
|
40
|
+
"description": "Generates headless Angular Tabs directives (root, list, tab, panel) coordinated over consumer-owned markup, following the W3C ARIA Authoring Practices Guide.",
|
|
41
41
|
"factory": "./tabs/index#tabs",
|
|
42
42
|
"schema": "./tabs/schema.json"
|
|
43
43
|
},
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"schema": "./tooltip/schema.json"
|
|
63
63
|
},
|
|
64
64
|
"menu-button": {
|
|
65
|
-
"description": "Generates
|
|
65
|
+
"description": "Generates headless Angular Menu Button directives (root, trigger, menu, item) coordinated over consumer-owned markup, following the W3C ARIA Authoring Practices Guide.",
|
|
66
66
|
"factory": "./menu-button/index#menuButton",
|
|
67
67
|
"schema": "./menu-button/schema.json"
|
|
68
68
|
},
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"schema": "./combobox/schema.json"
|
|
118
118
|
},
|
|
119
119
|
"breadcrumb": {
|
|
120
|
-
"description": "Generates
|
|
120
|
+
"description": "Generates headless Angular Breadcrumb directives (root, item) coordinated over consumer-owned markup, following the W3C ARIA Authoring Practices Guide.",
|
|
121
121
|
"factory": "./breadcrumb/index#breadcrumb",
|
|
122
122
|
"schema": "./breadcrumb/schema.json"
|
|
123
123
|
},
|
|
@@ -127,12 +127,12 @@
|
|
|
127
127
|
"schema": "./navigation-menu/schema.json"
|
|
128
128
|
},
|
|
129
129
|
"navbar": {
|
|
130
|
-
"description": "Generates
|
|
130
|
+
"description": "Generates headless Angular Navbar directives (root, item, toggle, menu) for semantic navigation, responsive disclosure toggle, and current-page state, coordinated over consumer-owned markup.",
|
|
131
131
|
"factory": "./navbar/index#navbar",
|
|
132
132
|
"schema": "./navbar/schema.json"
|
|
133
133
|
},
|
|
134
134
|
"pagination": {
|
|
135
|
-
"description": "Generates
|
|
135
|
+
"description": "Generates headless Angular Pagination directives (root, item) coordinated over consumer-owned markup.",
|
|
136
136
|
"factory": "./pagination/index#pagination",
|
|
137
137
|
"schema": "./pagination/schema.json"
|
|
138
138
|
},
|
|
@@ -147,12 +147,12 @@
|
|
|
147
147
|
"schema": "./landmarks/schema.json"
|
|
148
148
|
},
|
|
149
149
|
"tree-view": {
|
|
150
|
-
"description": "Generates
|
|
150
|
+
"description": "Generates headless Angular Tree View directives (root, item, group) coordinated over consumer-owned, arbitrarily nested markup, following the W3C ARIA Authoring Practices Guide.",
|
|
151
151
|
"factory": "./tree-view/index#treeView",
|
|
152
152
|
"schema": "./tree-view/schema.json"
|
|
153
153
|
},
|
|
154
154
|
"toolbar": {
|
|
155
|
-
"description": "Generates
|
|
155
|
+
"description": "Generates headless Angular Toolbar directives (root, item) coordinated over consumer-owned markup, following the W3C ARIA Authoring Practices Guide.",
|
|
156
156
|
"factory": "./toolbar/index#toolbar",
|
|
157
157
|
"schema": "./toolbar/schema.json"
|
|
158
158
|
},
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"schema": "./card/schema.json"
|
|
163
163
|
},
|
|
164
164
|
"table": {
|
|
165
|
-
"description": "Generates a standalone Angular
|
|
165
|
+
"description": "Generates a minimal standalone Angular table wrapper with content projection — no fixed column count or row shape — following the W3C ARIA Authoring Practices Guide.",
|
|
166
166
|
"factory": "./table/index#table",
|
|
167
167
|
"schema": "./table/schema.json"
|
|
168
168
|
},
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
position: relative;
|
|
3
|
-
display: inline-block;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
button {
|
|
7
|
-
display: flex;
|
|
8
|
-
align-items: center;
|
|
9
|
-
font: inherit;
|
|
10
|
-
cursor: pointer;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
button:focus-visible {
|
|
14
|
-
outline: 2px solid currentColor;
|
|
15
|
-
outline-offset: 2px;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
button::after {
|
|
19
|
-
content: '';
|
|
20
|
-
width: 0.4rem;
|
|
21
|
-
height: 0.4rem;
|
|
22
|
-
border-right: 2px solid currentColor;
|
|
23
|
-
border-bottom: 2px solid currentColor;
|
|
24
|
-
transform: rotate(45deg);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
button[aria-expanded='true']::after {
|
|
28
|
-
transform: rotate(-135deg);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
ul {
|
|
32
|
-
position: absolute;
|
|
33
|
-
z-index: 1;
|
|
34
|
-
top: 100%;
|
|
35
|
-
left: 0;
|
|
36
|
-
margin: 0;
|
|
37
|
-
padding: 0;
|
|
38
|
-
list-style: none;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
[role='menuitem'] {
|
|
42
|
-
cursor: pointer;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
[role='menuitem']:focus-visible {
|
|
46
|
-
outline: 2px solid currentColor;
|
|
47
|
-
outline-offset: 2px;
|
|
48
|
-
}
|
|
@@ -4,10 +4,10 @@ test.describe('<%= classify(name) %>', () => {
|
|
|
4
4
|
test('opens mounted menu button and closes it with Escape', async ({ page }) => {
|
|
5
5
|
await page.goto('/');
|
|
6
6
|
|
|
7
|
-
const component = page.locator('<%=
|
|
7
|
+
const component = page.locator('[<%= camelize(name) %>]').first();
|
|
8
8
|
test.skip(
|
|
9
9
|
(await component.count()) === 0,
|
|
10
|
-
'Mount
|
|
10
|
+
'Mount an element with [<%= camelize(name) %>] in a route or host component to enable this generated e2e test.'
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
const button = component.getByRole('button').first();
|
|
@@ -1,40 +1,19 @@
|
|
|
1
1
|
<!-- Semmet Angular: Menu Button — https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ -->
|
|
2
2
|
<!--
|
|
3
3
|
Usage from a parent component template:
|
|
4
|
-
Import <%= classify(name) %>
|
|
4
|
+
Import <%= classify(name) %>, <%= classify(name) %>Trigger, <%= classify(name) %>Menu and <%= classify(name) %>Item
|
|
5
|
+
in that parent component. This directive set never owns markup: bring your own wrapper, trigger
|
|
6
|
+
button and popup list, in whatever structure the layout needs.
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
<div <%= camelize(name) %> style="position: relative; display: inline-block;">
|
|
9
|
+
<button type="button" <%= camelize(name) %>Trigger>Options</button>
|
|
10
|
+
|
|
11
|
+
<ul <%= camelize(name) %>Menu style="position: absolute;">
|
|
12
|
+
@for (item of items; track item.id) {
|
|
13
|
+
<li role="none">
|
|
14
|
+
<div <%= camelize(name) %>Item>{{ item.label }}</div>
|
|
15
|
+
</li>
|
|
16
|
+
}
|
|
17
|
+
</ul>
|
|
18
|
+
</div>
|
|
7
19
|
-->
|
|
8
|
-
<button
|
|
9
|
-
#menuButton
|
|
10
|
-
type="button"
|
|
11
|
-
[id]="instanceId + '-button'"
|
|
12
|
-
aria-haspopup="true"
|
|
13
|
-
[attr.aria-expanded]="isOpen()"
|
|
14
|
-
[attr.aria-controls]="instanceId + '-menu'"
|
|
15
|
-
(click)="toggle()"
|
|
16
|
-
(keydown)="handleButtonKeydown($event)"
|
|
17
|
-
>
|
|
18
|
-
{{ label() }}
|
|
19
|
-
</button>
|
|
20
|
-
<ul
|
|
21
|
-
#menu
|
|
22
|
-
role="menu"
|
|
23
|
-
[id]="instanceId + '-menu'"
|
|
24
|
-
[attr.aria-labelledby]="instanceId + '-button'"
|
|
25
|
-
[hidden]="!isOpen()"
|
|
26
|
-
>
|
|
27
|
-
@for (item of items(); track item.id; let i = $index) {
|
|
28
|
-
<li role="none">
|
|
29
|
-
<div
|
|
30
|
-
#menuItem
|
|
31
|
-
role="menuitem"
|
|
32
|
-
tabindex="-1"
|
|
33
|
-
(click)="select(item)"
|
|
34
|
-
(keydown)="handleMenuKeydown($event, item, i)"
|
|
35
|
-
>
|
|
36
|
-
{{ item.label }}
|
|
37
|
-
</div>
|
|
38
|
-
</li>
|
|
39
|
-
}
|
|
40
|
-
</ul>
|
|
@@ -1,28 +1,60 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
1
2
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
<%= classify(name) %>,
|
|
6
|
+
<%= classify(name) %>Item,
|
|
7
|
+
<%= classify(name) %>Menu,
|
|
8
|
+
<%= classify(name) %>Trigger,
|
|
9
|
+
} from './<%= dasherize(name) %>';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
imports: [<%= classify(name) %>, <%= classify(name) %>Item, <%= classify(name) %>Menu, <%= classify(name) %>Trigger],
|
|
13
|
+
template: `
|
|
14
|
+
<div <%= camelize(name) %>>
|
|
15
|
+
<button type="button" <%= camelize(name) %>Trigger>Options</button>
|
|
16
|
+
<ul <%= camelize(name) %>Menu>
|
|
17
|
+
@for (item of items; track item.id) {
|
|
18
|
+
<li role="none">
|
|
19
|
+
<div <%= camelize(name) %>Item>{{ item.label }}</div>
|
|
20
|
+
</li>
|
|
21
|
+
}
|
|
22
|
+
</ul>
|
|
23
|
+
</div>
|
|
24
|
+
`,
|
|
25
|
+
})
|
|
26
|
+
class HostComponent {
|
|
27
|
+
readonly items = [
|
|
28
|
+
{ id: 1, label: 'First option' },
|
|
29
|
+
{ id: 2, label: 'Second option' },
|
|
30
|
+
{ id: 3, label: 'Third option' },
|
|
31
|
+
];
|
|
32
|
+
}
|
|
3
33
|
|
|
4
34
|
describe('<%= classify(name) %>', () => {
|
|
5
|
-
let fixture: ComponentFixture
|
|
35
|
+
let fixture: ComponentFixture<HostComponent>;
|
|
36
|
+
let host: HTMLElement;
|
|
6
37
|
|
|
7
38
|
beforeEach(async () => {
|
|
8
|
-
await TestBed.configureTestingModule({ imports: [
|
|
9
|
-
fixture = TestBed.createComponent(
|
|
39
|
+
await TestBed.configureTestingModule({ imports: [HostComponent] }).compileComponents();
|
|
40
|
+
fixture = TestBed.createComponent(HostComponent);
|
|
10
41
|
fixture.detectChanges();
|
|
42
|
+
host = fixture.nativeElement as HTMLElement;
|
|
11
43
|
});
|
|
12
44
|
|
|
13
45
|
function trigger(): HTMLButtonElement {
|
|
14
|
-
return
|
|
46
|
+
return host.querySelector('button')!;
|
|
15
47
|
}
|
|
16
48
|
|
|
17
49
|
function menu(): HTMLElement {
|
|
18
|
-
return
|
|
50
|
+
return host.querySelector('[role="menu"]')!;
|
|
19
51
|
}
|
|
20
52
|
|
|
21
53
|
function menuItems(): HTMLElement[] {
|
|
22
|
-
return Array.from(
|
|
54
|
+
return Array.from(host.querySelectorAll('[role="menuitem"]'));
|
|
23
55
|
}
|
|
24
56
|
|
|
25
|
-
it('starts closed with menu button semantics', () => {
|
|
57
|
+
it('starts closed with consumer-owned menu button semantics', () => {
|
|
26
58
|
expect(trigger().getAttribute('aria-haspopup')).toBe('true');
|
|
27
59
|
expect(trigger().getAttribute('aria-expanded')).toBe('false');
|
|
28
60
|
expect(trigger().getAttribute('aria-controls')).toBe(menu().id);
|
|
@@ -39,7 +71,7 @@ describe('<%= classify(name) %>', () => {
|
|
|
39
71
|
expect(menuItems()[0].textContent?.trim()).toBe('First option');
|
|
40
72
|
});
|
|
41
73
|
|
|
42
|
-
it('opens with keyboard and closes with Escape', () => {
|
|
74
|
+
it('opens with keyboard and closes with Escape, returning focus to the trigger', () => {
|
|
43
75
|
trigger().dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
44
76
|
fixture.detectChanges();
|
|
45
77
|
expect(menu().hidden).toBe(false);
|
|
@@ -51,14 +83,24 @@ describe('<%= classify(name) %>', () => {
|
|
|
51
83
|
expect(document.activeElement).toBe(trigger());
|
|
52
84
|
});
|
|
53
85
|
|
|
54
|
-
it('moves focus between menu items', () => {
|
|
86
|
+
it('moves focus between menu items with ArrowDown/ArrowUp', () => {
|
|
55
87
|
trigger().click();
|
|
56
88
|
fixture.detectChanges();
|
|
57
89
|
|
|
58
|
-
menuItems()[0].focus();
|
|
59
90
|
menuItems()[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
60
91
|
fixture.detectChanges();
|
|
61
92
|
|
|
62
93
|
expect(document.activeElement).toBe(menuItems()[1]);
|
|
63
94
|
});
|
|
95
|
+
|
|
96
|
+
it('closes on an outside click without selecting an item', () => {
|
|
97
|
+
trigger().click();
|
|
98
|
+
fixture.detectChanges();
|
|
99
|
+
|
|
100
|
+
document.body.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
101
|
+
fixture.detectChanges();
|
|
102
|
+
|
|
103
|
+
expect(trigger().getAttribute('aria-expanded')).toBe('false');
|
|
104
|
+
expect(menu().hidden).toBe(true);
|
|
105
|
+
});
|
|
64
106
|
});
|