semmet-angular 0.45.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.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/src/accordion/files/__name@dasherize__/__name@dasherize__.css.template +0 -39
  3. package/src/accordion/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +16 -17
  4. package/src/accordion/files/__name@dasherize__/__name@dasherize__.html.template +14 -34
  5. package/src/accordion/files/__name@dasherize__/__name@dasherize__.spec.ts.template +52 -62
  6. package/src/accordion/files/__name@dasherize__/__name@dasherize__.ts.template +88 -82
  7. package/src/accordion/schema.json +1 -1
  8. package/src/block/files/__name@dasherize__/__name@dasherize__.html.template +51 -23
  9. package/src/block/files/__name@dasherize__/__name@dasherize__.ts.template +16 -10
  10. package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.css.template +0 -32
  11. package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +5 -2
  12. package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.html.template +8 -7
  13. package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.spec.ts.template +51 -0
  14. package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.ts.template +18 -19
  15. package/src/breadcrumb/index.js +1 -1
  16. package/src/breadcrumb/index.js.map +1 -1
  17. package/src/breadcrumb/index.ts +1 -1
  18. package/src/breadcrumb/schema.json +1 -1
  19. package/src/collection.json +9 -9
  20. package/src/menu-button/files/__name@dasherize__/__name@dasherize__.css.template +0 -48
  21. package/src/menu-button/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
  22. package/src/menu-button/files/__name@dasherize__/__name@dasherize__.html.template +14 -35
  23. package/src/menu-button/files/__name@dasherize__/__name@dasherize__.spec.ts.template +53 -11
  24. package/src/menu-button/files/__name@dasherize__/__name@dasherize__.ts.template +128 -54
  25. package/src/menu-button/schema.json +1 -1
  26. package/src/navbar/files/__name@dasherize__/__name@dasherize__.css.template +0 -119
  27. package/src/navbar/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +10 -7
  28. package/src/navbar/files/__name@dasherize__/__name@dasherize__.html.template +18 -42
  29. package/src/navbar/files/__name@dasherize__/__name@dasherize__.spec.ts.template +46 -23
  30. package/src/navbar/files/__name@dasherize__/__name@dasherize__.ts.template +54 -50
  31. package/src/navbar/schema.json +1 -1
  32. package/src/pagination/files/__name@dasherize__/__name@dasherize__.css.template +0 -35
  33. package/src/pagination/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
  34. package/src/pagination/files/__name@dasherize__/__name@dasherize__.html.template +11 -13
  35. package/src/pagination/files/__name@dasherize__/__name@dasherize__.spec.ts.template +58 -8
  36. package/src/pagination/files/__name@dasherize__/__name@dasherize__.ts.template +17 -31
  37. package/src/pagination/schema.json +1 -1
  38. package/src/table/files/__name@dasherize__/__name@dasherize__.css.template +0 -17
  39. package/src/table/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +1 -1
  40. package/src/table/files/__name@dasherize__/__name@dasherize__.html.template +13 -25
  41. package/src/table/files/__name@dasherize__/__name@dasherize__.ts.template +3 -35
  42. package/src/table/schema.json +1 -1
  43. package/src/tabs/files/__name@dasherize__/__name@dasherize__.css.template +0 -40
  44. package/src/tabs/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +2 -2
  45. package/src/tabs/files/__name@dasherize__/__name@dasherize__.html.template +18 -41
  46. package/src/tabs/files/__name@dasherize__/__name@dasherize__.spec.ts.template +39 -31
  47. package/src/tabs/files/__name@dasherize__/__name@dasherize__.ts.template +111 -113
  48. package/src/tabs/schema.json +1 -1
  49. package/src/toolbar/files/__name@dasherize__/__name@dasherize__.css.template +0 -19
  50. package/src/toolbar/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +3 -4
  51. package/src/toolbar/files/__name@dasherize__/__name@dasherize__.html.template +7 -16
  52. package/src/toolbar/files/__name@dasherize__/__name@dasherize__.spec.ts.template +26 -11
  53. package/src/toolbar/files/__name@dasherize__/__name@dasherize__.ts.template +59 -37
  54. package/src/toolbar/schema.json +1 -1
  55. package/src/tree-view/files/__name@dasherize__/__name@dasherize__.css.template +0 -39
  56. package/src/tree-view/files/__name@dasherize__/__name@dasherize__.e2e-spec.ts.template +4 -5
  57. package/src/tree-view/files/__name@dasherize__/__name@dasherize__.html.template +12 -31
  58. package/src/tree-view/files/__name@dasherize__/__name@dasherize__.spec.ts.template +80 -43
  59. package/src/tree-view/files/__name@dasherize__/__name@dasherize__.ts.template +105 -88
  60. package/src/tree-view/schema.json +1 -1
@@ -1,64 +1,68 @@
1
- import { NgTemplateOutlet } from '@angular/common';
2
- import { Component, Directive, TemplateRef, computed, contentChild, inject, input, signal } from '@angular/core';
3
-
4
- export interface <%= classify(name) %>Item {
5
- label: string;
6
- href: string;
7
- current?: boolean;
8
- disabled?: boolean;
9
- }
10
-
11
- const DEFAULT_ITEMS: readonly <%= classify(name) %>Item[] = [
12
- { label: 'Home', href: '/', current: true },
13
- { label: 'Projects', href: '/projects' },
14
- { label: 'About', href: '/about' },
15
- { label: 'Contact', href: '/contact' },
16
- ];
1
+ import { booleanAttribute, computed, Directive, inject, input, model } from '@angular/core';
17
2
 
18
3
  let nextId = 0;
19
4
 
20
5
  @Directive({
21
- selector: 'ng-template[<%= camelize(name) %>Brand]',
22
- })
23
- export class <%= classify(name) %>Brand {
24
- readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
25
- }
26
-
27
- @Directive({
28
- selector: 'ng-template[<%= camelize(name) %>Actions]',
29
- })
30
- export class <%= classify(name) %>Actions {
31
- readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
32
- }
33
-
34
- @Component({
35
- selector: '<%= selector %>',
36
- imports: [NgTemplateOutlet],
37
- templateUrl: './<%= dasherize(name) %>.html',
38
- styleUrl: './<%= dasherize(name) %>.css',
6
+ selector: '[<%= camelize(name) %>]',
7
+ exportAs: '<%= camelize(name) %>',
8
+ host: {
9
+ '[attr.aria-label]': 'label() || null',
10
+ },
39
11
  })
40
12
  export class <%= classify(name) %> {
41
- protected readonly brandTemplate = contentChild(<%= classify(name) %>Brand);
42
- protected readonly actions = contentChild(<%= classify(name) %>Actions);
43
- protected readonly instanceId = `<%= dasherize(name) %>-${nextId++}`;
44
- protected readonly expanded = signal(false);
45
-
46
- readonly label = input('Primary navigation');
47
- readonly brand = input('Semmet');
48
- readonly brandHref = input('/');
49
- readonly toggleLabel = input('Toggle navigation');
50
- readonly items = input<readonly <%= classify(name) %>Item[]>(DEFAULT_ITEMS);
13
+ private readonly instanceId = `<%= dasherize(name) %>-${nextId++}`;
51
14
 
52
- protected readonly menuClasses = computed(() => [
53
- 'semmet-navbar__menu',
54
- this.expanded() ? 'semmet-navbar__menu--open' : '',
55
- ].filter(Boolean).join(' '));
15
+ readonly label = input('');
16
+ readonly expanded = model(false);
17
+ readonly menuId = computed(() => `${this.instanceId}-menu`);
56
18
 
57
- protected toggle(): void {
19
+ toggle(): void {
58
20
  this.expanded.update((value) => !value);
59
21
  }
60
22
 
61
- protected close(): void {
23
+ close(): void {
62
24
  this.expanded.set(false);
63
25
  }
64
26
  }
27
+
28
+ @Directive({
29
+ selector: '[<%= camelize(name) %>Item]',
30
+ exportAs: '<%= camelize(name) %>Item',
31
+ host: {
32
+ '[attr.aria-current]': 'current() ? ariaCurrent() : null',
33
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
34
+ '(click)': 'nav.close()',
35
+ },
36
+ })
37
+ export class <%= classify(name) %>Item {
38
+ protected readonly nav = inject(<%= classify(name) %>);
39
+
40
+ readonly current = input(false, { transform: booleanAttribute });
41
+ readonly disabled = input(false, { transform: booleanAttribute });
42
+ readonly ariaCurrent = input('page');
43
+ }
44
+
45
+ @Directive({
46
+ selector: '[<%= camelize(name) %>Toggle]',
47
+ exportAs: '<%= camelize(name) %>Toggle',
48
+ host: {
49
+ '[attr.aria-expanded]': 'nav.expanded()',
50
+ '[attr.aria-controls]': 'nav.menuId()',
51
+ '(click)': 'nav.toggle()',
52
+ },
53
+ })
54
+ export class <%= classify(name) %>Toggle {
55
+ protected readonly nav = inject(<%= classify(name) %>);
56
+ }
57
+
58
+ @Directive({
59
+ selector: '[<%= camelize(name) %>Menu]',
60
+ exportAs: '<%= camelize(name) %>Menu',
61
+ host: {
62
+ '[id]': 'nav.menuId()',
63
+ '[hidden]': '!nav.expanded()',
64
+ },
65
+ })
66
+ export class <%= classify(name) %>Menu {
67
+ protected readonly nav = inject(<%= classify(name) %>);
68
+ }
@@ -3,7 +3,7 @@
3
3
  "$id": "SemmetAngularNavbar",
4
4
  "title": "Semmet Angular Navbar Options Schema",
5
5
  "type": "object",
6
- "description": "Generates a standalone Angular Navbar component with semantic navigation, responsive disclosure toggle, and current-page state.",
6
+ "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.",
7
7
  "additionalProperties": false,
8
8
  "properties": {
9
9
  "name": {
@@ -1,35 +0,0 @@
1
- :host {
2
- display: block;
3
- }
4
-
5
- ul {
6
- display: flex;
7
- align-items: center;
8
- margin: 0;
9
- padding: 0;
10
- list-style: none;
11
- }
12
-
13
- a,
14
- button {
15
- display: inline-flex;
16
- align-items: center;
17
- justify-content: center;
18
- font: inherit;
19
- color: inherit;
20
- background: none;
21
- text-decoration: none;
22
- cursor: pointer;
23
- box-sizing: border-box;
24
- }
25
-
26
- a:focus-visible,
27
- button:focus-visible {
28
- outline: 2px solid currentColor;
29
- outline-offset: 2px;
30
- }
31
-
32
- button:disabled {
33
- opacity: 0.5;
34
- cursor: not-allowed;
35
- }
@@ -4,10 +4,10 @@ test.describe('<%= classify(name) %>', () => {
4
4
  test('moves mounted pagination current page with next and previous controls', async ({ page }) => {
5
5
  await page.goto('/');
6
6
 
7
- const component = page.locator('<%= selector %>').first();
7
+ const component = page.locator('[<%= camelize(name) %>]').first();
8
8
  test.skip(
9
9
  (await component.count()) === 0,
10
- 'Mount <<%= selector %> /> in a route or host component to enable this generated e2e test.'
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 current = component.locator('[aria-current="page"]').first();
@@ -1,32 +1,30 @@
1
1
  <!-- Semmet Angular: Pagination — https://www.w3.org/TR/wai-aria-1.2/#aria-current -->
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/ul/li structure, previous/next
6
+ controls and page links. Native `disabled`/`aria-label` on the previous/next buttons
7
+ already communicate everything they need — no directive required for those.
5
8
 
6
- <<%= selector %> label="Search results pages" />
7
- -->
8
- <nav [attr.aria-label]="label()">
9
+ <nav <%= camelize(name) %> label="Search results pages">
9
10
  <ul>
10
11
  <li>
11
- <button type="button" [disabled]="!hasPrevious()" (click)="goToPrevious()" [attr.aria-label]="previousAriaLabel()">
12
- {{ previousLabel() }}
12
+ <button type="button" [disabled]="!hasPrevious()" (click)="goToPrevious()" aria-label="Previous page">
13
+ Previous
13
14
  </button>
14
15
  </li>
15
16
  @for (page of pages; track page) {
16
17
  <li>
17
- <a
18
- [href]="'?page=' + page"
19
- [attr.aria-current]="page === currentPage() ? 'page' : null"
20
- (click)="goToPage(page)"
21
- >
18
+ <a <%= camelize(name) %>Item [href]="'?page=' + page" [current]="page === currentPage()" (click)="goToPage(page)">
22
19
  {{ page }}
23
20
  </a>
24
21
  </li>
25
22
  }
26
23
  <li>
27
- <button type="button" [disabled]="!hasNext()" (click)="goToNext()" [attr.aria-label]="nextAriaLabel()">
28
- {{ nextLabel() }}
24
+ <button type="button" [disabled]="!hasNext()" (click)="goToNext()" aria-label="Next page">
25
+ Next
29
26
  </button>
30
27
  </li>
31
28
  </ul>
32
29
  </nav>
30
+ -->
@@ -1,25 +1,75 @@
1
+ import { Component, computed, signal } from '@angular/core';
1
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { <%= classify(name) %> } from './<%= dasherize(name) %>';
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
+ <ul>
11
+ <li>
12
+ <button type="button" [disabled]="!hasPrevious()" (click)="goToPrevious()">Previous</button>
13
+ </li>
14
+ @for (page of pages; track page) {
15
+ <li>
16
+ <a <%= camelize(name) %>Item [href]="'?page=' + page" [current]="page === currentPage()" (click)="goToPage(page)">
17
+ {{ page }}
18
+ </a>
19
+ </li>
20
+ }
21
+ <li>
22
+ <button type="button" [disabled]="!hasNext()" (click)="goToNext()">Next</button>
23
+ </li>
24
+ </ul>
25
+ </nav>
26
+ `,
27
+ })
28
+ class HostComponent {
29
+ readonly pages = [1, 2, 3, 4, 5];
30
+ readonly currentPage = signal(1);
31
+
32
+ readonly hasPrevious = computed(() => this.currentPage() > 1);
33
+ readonly hasNext = computed(() => this.currentPage() < this.pages.length);
34
+
35
+ goToPage(page: number): void {
36
+ this.currentPage.set(page);
37
+ }
38
+
39
+ goToPrevious(): void {
40
+ if (this.hasPrevious()) {
41
+ this.currentPage.update((page) => page - 1);
42
+ }
43
+ }
44
+
45
+ goToNext(): void {
46
+ if (this.hasNext()) {
47
+ this.currentPage.update((page) => page + 1);
48
+ }
49
+ }
50
+ }
3
51
 
4
52
  describe('<%= classify(name) %>', () => {
5
- let fixture: ComponentFixture<<%= classify(name) %>>;
53
+ let fixture: ComponentFixture<HostComponent>;
54
+ let host: HTMLElement;
6
55
 
7
56
  beforeEach(async () => {
8
- await TestBed.configureTestingModule({ imports: [<%= classify(name) %>] }).compileComponents();
9
- fixture = TestBed.createComponent(<%= classify(name) %>);
57
+ await TestBed.configureTestingModule({ imports: [HostComponent] }).compileComponents();
58
+ fixture = TestBed.createComponent(HostComponent);
10
59
  fixture.detectChanges();
60
+ host = fixture.nativeElement as HTMLElement;
11
61
  });
12
62
 
13
63
  function buttons(): HTMLButtonElement[] {
14
- return Array.from(fixture.nativeElement.querySelectorAll('button'));
64
+ return Array.from(host.querySelectorAll('button'));
15
65
  }
16
66
 
17
67
  function links(): HTMLAnchorElement[] {
18
- return Array.from(fixture.nativeElement.querySelectorAll('a'));
68
+ return Array.from(host.querySelectorAll('a'));
19
69
  }
20
70
 
21
- it('renders navigation with current page state', () => {
22
- expect(fixture.nativeElement.querySelector('nav').getAttribute('aria-label')).toBe('Pagination');
71
+ it('decorates consumer-owned markup with navigation and current-page state', () => {
72
+ expect(host.querySelector('nav')?.getAttribute('aria-label')).toBe('Pagination');
23
73
  expect(buttons()[0].disabled).toBe(true);
24
74
  expect(links()[0].getAttribute('aria-current')).toBe('page');
25
75
  expect(buttons()[1].disabled).toBe(false);
@@ -1,37 +1,23 @@
1
- import { Component, computed, input, signal } from '@angular/core';
1
+ import { Directive, booleanAttribute, input } from '@angular/core';
2
2
 
3
- @Component({
4
- selector: '<%= selector %>',
5
- imports: [],
6
- templateUrl: './<%= dasherize(name) %>.html',
7
- styleUrl: './<%= dasherize(name) %>.css',
3
+ @Directive({
4
+ selector: '[<%= camelize(name) %>]',
5
+ exportAs: '<%= camelize(name) %>',
6
+ host: {
7
+ '[attr.aria-label]': 'label() || null',
8
+ },
8
9
  })
9
10
  export class <%= classify(name) %> {
10
11
  readonly label = input('Pagination');
11
- readonly previousLabel = input('Previous');
12
- readonly previousAriaLabel = input('Previous page');
13
- readonly nextLabel = input('Next');
14
- readonly nextAriaLabel = input('Next page');
15
-
16
- protected readonly pages = [1, 2, 3, 4, 5];
17
- protected readonly currentPage = signal(1);
18
-
19
- protected readonly hasPrevious = computed(() => this.currentPage() > 1);
20
- protected readonly hasNext = computed(() => this.currentPage() < this.pages.length);
21
-
22
- protected goToPage(page: number): void {
23
- this.currentPage.set(page);
24
- }
25
-
26
- protected goToPrevious(): void {
27
- if (this.hasPrevious()) {
28
- this.currentPage.update((page) => page - 1);
29
- }
30
- }
12
+ }
31
13
 
32
- protected goToNext(): void {
33
- if (this.hasNext()) {
34
- this.currentPage.update((page) => page + 1);
35
- }
36
- }
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 });
37
23
  }
@@ -3,7 +3,7 @@
3
3
  "$id": "SemmetAngularPagination",
4
4
  "title": "Semmet Angular Pagination Options Schema",
5
5
  "type": "object",
6
- "description": "Generates a standalone Angular Pagination component with working page state, following W3C ARIA guidance on aria-current.",
6
+ "description": "Generates headless Angular Pagination directives (root, item) coordinated over consumer-owned markup, following W3C ARIA guidance on aria-current.",
7
7
  "additionalProperties": false,
8
8
  "properties": {
9
9
  "name": {
@@ -1,17 +0,0 @@
1
- :host {
2
- display: block;
3
- }
4
-
5
- table {
6
- width: 100%;
7
- border-collapse: collapse;
8
- }
9
-
10
- caption {
11
- text-align: left;
12
- }
13
-
14
- th,
15
- td {
16
- text-align: left;
17
- }
@@ -9,7 +9,7 @@ test.describe('<%= classify(name) %>', () => {
9
9
  const table = component.locator('table').first();
10
10
  await expect(table).toBeVisible();
11
11
  await expect(table.locator('caption')).toBeVisible();
12
- await expect(table.locator('th[scope="col"]')).toHaveCount(2);
12
+ await expect(table.locator('th[scope="col"]')).not.toHaveCount(0);
13
13
  await expect(table.locator('tbody tr')).not.toHaveCount(0);
14
14
  });
15
15
  });
@@ -1,45 +1,33 @@
1
1
  <!-- Semmet Angular: Table — https://www.w3.org/WAI/ARIA/apg/patterns/table/ -->
2
2
  <!--
3
3
  Usage from a parent component template:
4
- Import <%= classify(name) %> in that parent component.
4
+ Import <%= classify(name) %> in that parent component. This component only owns the <table>
5
+ wrapper — caption, column count, row shape, and the empty state are entirely up to what you
6
+ project. A <caption> as the first projected child already gives the table its accessible name
7
+ natively; no aria-labelledby wiring needed.
5
8
 
6
- <<%= selector %> caption="Quarterly revenue" [rows]="results" />
7
9
  <<%= selector %>>
8
- <ng-template <%= camelize(name) %>Caption>
9
- Quarterly results, fiscal year 2026
10
- </ng-template>
11
- </<%= selector %>>
12
- -->
13
- <table>
14
- <caption>
15
- @if (captionTemplate(); as captionSlot) {
16
- <ng-container [ngTemplateOutlet]="captionSlot.templateRef" />
17
- } @else {
18
- {{ caption() }}
19
- }
20
- </caption>
10
+ <caption>Quarterly revenue, fiscal year 2026</caption>
21
11
  <thead>
22
12
  <tr>
23
- <th scope="col">{{ quarterHeader() }}</th>
24
- <th scope="col">{{ revenueHeader() }}</th>
13
+ <th scope="col">Quarter</th>
14
+ <th scope="col">Revenue</th>
25
15
  </tr>
26
16
  </thead>
27
17
  <tbody>
28
- @for (row of rows(); track row.quarter) {
18
+ @for (row of results; track row.quarter) {
29
19
  <tr>
30
20
  <th scope="row">{{ row.quarter }}</th>
31
21
  <td>{{ row.revenue }}</td>
32
22
  </tr>
33
23
  } @empty {
34
24
  <tr>
35
- <td colspan="2">
36
- @if (emptyTemplate(); as emptySlot) {
37
- <ng-container [ngTemplateOutlet]="emptySlot.templateRef" />
38
- } @else {
39
- {{ emptyLabel() }}
40
- }
41
- </td>
25
+ <td colspan="2">No results available.</td>
42
26
  </tr>
43
27
  }
44
28
  </tbody>
29
+ </<%= selector %>>
30
+ -->
31
+ <table>
32
+ <ng-content />
45
33
  </table>
@@ -1,41 +1,9 @@
1
- import { NgTemplateOutlet } from '@angular/common';
2
- import { Component, Directive, TemplateRef, contentChild, inject, input } from '@angular/core';
3
-
4
- export interface <%= classify(name) %>Row {
5
- quarter: string;
6
- revenue: string;
7
- }
8
-
9
- @Directive({
10
- selector: 'ng-template[<%= camelize(name) %>Caption]',
11
- })
12
- export class <%= classify(name) %>Caption {
13
- readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
14
- }
15
-
16
- @Directive({
17
- selector: 'ng-template[<%= camelize(name) %>Empty]',
18
- })
19
- export class <%= classify(name) %>Empty {
20
- readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef);
21
- }
1
+ import { Component } from '@angular/core';
22
2
 
23
3
  @Component({
24
4
  selector: '<%= selector %>',
25
- imports: [NgTemplateOutlet],
5
+ imports: [],
26
6
  templateUrl: './<%= dasherize(name) %>.html',
27
7
  styleUrl: './<%= dasherize(name) %>.css',
28
8
  })
29
- export class <%= classify(name) %> {
30
- protected readonly captionTemplate = contentChild(<%= classify(name) %>Caption);
31
- protected readonly emptyTemplate = contentChild(<%= classify(name) %>Empty);
32
-
33
- readonly caption = input('Quarterly results');
34
- readonly quarterHeader = input('Quarter');
35
- readonly revenueHeader = input('Revenue');
36
- readonly emptyLabel = input('No results available.');
37
- readonly rows = input<readonly <%= classify(name) %>Row[]>([
38
- { quarter: 'Q1', revenue: '$12,000' },
39
- { quarter: 'Q2', revenue: '$15,500' },
40
- ]);
41
- }
9
+ export class <%= classify(name) %> {}
@@ -3,7 +3,7 @@
3
3
  "$id": "SemmetAngularTable",
4
4
  "title": "Semmet Angular Table Options Schema",
5
5
  "type": "object",
6
- "description": "Generates a standalone Angular accessible data table component (caption, scoped headers), following the W3C ARIA Authoring Practices Guide.",
6
+ "description": "Generates a minimal standalone Angular table wrapper (a bare <table> with content projection) — caption, column count, and row shape are entirely up to what you project, following the W3C ARIA Authoring Practices Guide.",
7
7
  "additionalProperties": false,
8
8
  "properties": {
9
9
  "name": {
@@ -1,40 +0,0 @@
1
- :host {
2
- display: block;
3
- }
4
-
5
- [role='tablist'] {
6
- display: flex;
7
- }
8
-
9
- [role='tab'] {
10
- position: relative;
11
- font: inherit;
12
- background: none;
13
- border: none;
14
- cursor: pointer;
15
- }
16
-
17
- [role='tab']:focus-visible {
18
- outline: 2px solid currentColor;
19
- outline-offset: 2px;
20
- }
21
-
22
- [role='tab']::after {
23
- content: '';
24
- position: absolute;
25
- left: 0;
26
- right: 0;
27
- bottom: -1px;
28
- height: 2px;
29
- background: currentColor;
30
- transform: scaleX(0);
31
- }
32
-
33
- [role='tab'][aria-selected='true']::after {
34
- transform: scaleX(1);
35
- }
36
-
37
- [role='tabpanel']:focus-visible {
38
- outline: 2px solid currentColor;
39
- outline-offset: 2px;
40
- }
@@ -4,10 +4,10 @@ test.describe('<%= classify(name) %>', () => {
4
4
  test('switches mounted tabs through click and keyboard navigation', async ({ page }) => {
5
5
  await page.goto('/');
6
6
 
7
- const component = page.locator('<%= selector %>').first();
7
+ const component = page.locator('[<%= camelize(name) %>]').first();
8
8
  test.skip(
9
9
  (await component.count()) === 0,
10
- 'Mount <<%= selector %> /> in a route or host component to enable this generated e2e test.'
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
  await expect(component).toBeVisible();
@@ -1,48 +1,25 @@
1
1
  <!-- Semmet Angular: Tabs — https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ -->
2
2
  <!--
3
3
  Usage from a parent component template:
4
- Import <%= classify(name) %> and <%= classify(name) %>Item in that parent component.
4
+ Import <%= classify(name) %>, <%= classify(name) %>List, <%= classify(name) %>Tab and <%= classify(name) %>Panel
5
+ in that parent component. This directive set never owns markup: bring your own wrapper, tab strip,
6
+ buttons and panels, in whatever structure the layout needs.
5
7
 
6
- <<%= selector %> label="Tabs">
7
- <ng-template <%= camelize(name) %>Item id="profile" label="Profile" [selected]="true">
8
- <app-profile-panel />
9
- </ng-template>
8
+ <div <%= camelize(name) %> [(activeId)]="activeTab">
9
+ <div <%= camelize(name) %>List label="Account">
10
+ <button type="button" <%= camelize(name) %>Tab value="profile" [selected]="true">Profile</button>
11
+ <button type="button" <%= camelize(name) %>Tab value="settings">Settings</button>
12
+ <button type="button" <%= camelize(name) %>Tab value="billing" [disabled]="true">Billing</button>
13
+ </div>
10
14
 
11
- <ng-template <%= camelize(name) %>Item id="settings" label="Settings">
15
+ <div <%= camelize(name) %>Panel value="profile">
16
+ <app-profile-panel />
17
+ </div>
18
+ <div <%= camelize(name) %>Panel value="settings">
12
19
  <app-settings-panel [userId]="userId()" (saved)="reload()" />
13
- </ng-template>
14
- </<%= selector %>>
15
-
16
- Set [preserveContent]="false" on <<%= selector %>> to destroy inactive tab content.
17
- -->
18
- <div role="tablist" [attr.aria-label]="label()">
19
- @for (item of items(); track item.id(); let i = $index) {
20
- <button
21
- #tabButton
22
- type="button"
23
- role="tab"
24
- [disabled]="item.disabled()"
25
- [attr.aria-selected]="isActive(item.id())"
26
- [attr.aria-controls]="instanceId + '-panel-' + item.id()"
27
- [id]="instanceId + '-tab-' + item.id()"
28
- [tabIndex]="isActive(item.id()) ? 0 : -1"
29
- (click)="select(item)"
30
- (keydown)="navigateTabs($event, i)"
31
- >
32
- {{ item.label() }}
33
- </button>
34
- }
35
- </div>
36
- @for (item of items(); track item.id()) {
37
- <div
38
- [id]="instanceId + '-panel-' + item.id()"
39
- role="tabpanel"
40
- tabindex="0"
41
- [attr.aria-labelledby]="instanceId + '-tab-' + item.id()"
42
- [hidden]="!isActive(item.id())"
43
- >
44
- @if (shouldRender(item.id())) {
45
- <ng-container [ngTemplateOutlet]="item.templateRef" />
46
- }
47
20
  </div>
48
- }
21
+ <div <%= camelize(name) %>Panel value="billing">
22
+ <app-billing-panel />
23
+ </div>
24
+ </div>
25
+ -->