semmet-angular 0.11.0 → 0.13.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 +15 -5
- package/package.json +1 -1
- package/src/alert/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/alert-dialog/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/badge/files/__name@dasherize__/__name@dasherize__.css.template +102 -0
- package/src/badge/files/__name@dasherize__/__name@dasherize__.html.template +21 -0
- package/src/badge/files/__name@dasherize__/__name@dasherize__.ts.template +43 -0
- package/src/badge/index.d.ts +1 -0
- package/src/badge/index.js +6 -0
- package/src/badge/index.js.map +1 -0
- package/src/badge/index.ts +3 -0
- package/src/badge/schema.d.ts +1 -0
- package/src/badge/schema.js +3 -0
- package/src/badge/schema.js.map +1 -0
- package/src/badge/schema.json +29 -0
- package/src/badge/schema.ts +1 -0
- package/src/breadcrumb/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/button/files/__name@dasherize__/__name@dasherize__.html.template +7 -0
- package/src/button-group/files/__name@dasherize__/__name@dasherize__.css.template +93 -0
- package/src/button-group/files/__name@dasherize__/__name@dasherize__.html.template +26 -0
- package/src/button-group/files/__name@dasherize__/__name@dasherize__.ts.template +85 -0
- package/src/button-group/index.d.ts +1 -0
- package/src/button-group/index.js +6 -0
- package/src/button-group/index.js.map +1 -0
- package/src/button-group/index.ts +3 -0
- package/src/button-group/schema.d.ts +1 -0
- package/src/button-group/schema.js +3 -0
- package/src/button-group/schema.js.map +1 -0
- package/src/button-group/schema.json +29 -0
- package/src/button-group/schema.ts +1 -0
- package/src/card/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/checkbox/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/close-button/files/__name@dasherize__/__name@dasherize__.css.template +105 -0
- package/src/close-button/files/__name@dasherize__/__name@dasherize__.html.template +17 -0
- package/src/close-button/files/__name@dasherize__/__name@dasherize__.ts.template +38 -0
- package/src/close-button/index.d.ts +1 -0
- package/src/close-button/index.js +6 -0
- package/src/close-button/index.js.map +1 -0
- package/src/close-button/index.ts +3 -0
- package/src/close-button/schema.d.ts +1 -0
- package/src/close-button/schema.js +3 -0
- package/src/close-button/schema.js.map +1 -0
- package/src/close-button/schema.json +29 -0
- package/src/close-button/schema.ts +1 -0
- package/src/collection.json +50 -0
- package/src/combobox/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/dialog/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/form/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/input/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/input-group/files/__name@dasherize__/__name@dasherize__.css.template +119 -0
- package/src/input-group/files/__name@dasherize__/__name@dasherize__.html.template +57 -0
- package/src/input-group/files/__name@dasherize__/__name@dasherize__.ts.template +64 -0
- package/src/input-group/index.d.ts +1 -0
- package/src/input-group/index.js +6 -0
- package/src/input-group/index.js.map +1 -0
- package/src/input-group/index.ts +3 -0
- package/src/input-group/schema.d.ts +1 -0
- package/src/input-group/schema.js +3 -0
- package/src/input-group/schema.js.map +1 -0
- package/src/input-group/schema.json +29 -0
- package/src/input-group/schema.ts +1 -0
- package/src/landmarks/files/__name@dasherize__/__name@dasherize__.html.template +12 -0
- package/src/list-group/files/__name@dasherize__/__name@dasherize__.css.template +121 -0
- package/src/list-group/files/__name@dasherize__/__name@dasherize__.html.template +53 -0
- package/src/list-group/files/__name@dasherize__/__name@dasherize__.ts.template +55 -0
- package/src/list-group/index.d.ts +1 -0
- package/src/list-group/index.js +6 -0
- package/src/list-group/index.js.map +1 -0
- package/src/list-group/index.ts +3 -0
- package/src/list-group/schema.d.ts +1 -0
- package/src/list-group/schema.js +3 -0
- package/src/list-group/schema.js.map +1 -0
- package/src/list-group/schema.json +29 -0
- package/src/list-group/schema.ts +1 -0
- package/src/listbox/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/menu-button/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/meter/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.css.template +158 -0
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.html.template +42 -0
- package/src/navbar/files/__name@dasherize__/__name@dasherize__.ts.template +47 -0
- package/src/navbar/index.d.ts +1 -0
- package/src/navbar/index.js +6 -0
- package/src/navbar/index.js.map +1 -0
- package/src/navbar/index.ts +3 -0
- package/src/navbar/schema.d.ts +1 -0
- package/src/navbar/schema.js +3 -0
- package/src/navbar/schema.js.map +1 -0
- package/src/navbar/schema.json +29 -0
- package/src/navbar/schema.ts +1 -0
- package/src/navigation-menu/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/offcanvas/files/__name@dasherize__/__name@dasherize__.css.template +180 -0
- package/src/offcanvas/files/__name@dasherize__/__name@dasherize__.html.template +60 -0
- package/src/offcanvas/files/__name@dasherize__/__name@dasherize__.ts.template +96 -0
- package/src/offcanvas/index.d.ts +1 -0
- package/src/offcanvas/index.js +6 -0
- package/src/offcanvas/index.js.map +1 -0
- package/src/offcanvas/index.ts +3 -0
- package/src/offcanvas/schema.d.ts +1 -0
- package/src/offcanvas/schema.js +3 -0
- package/src/offcanvas/schema.js.map +1 -0
- package/src/offcanvas/schema.json +29 -0
- package/src/offcanvas/schema.ts +1 -0
- package/src/pagination/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/popover/files/__name@dasherize__/__name@dasherize__.css.template +156 -0
- package/src/popover/files/__name@dasherize__/__name@dasherize__.html.template +48 -0
- package/src/popover/files/__name@dasherize__/__name@dasherize__.ts.template +65 -0
- package/src/popover/index.d.ts +1 -0
- package/src/popover/index.js +6 -0
- package/src/popover/index.js.map +1 -0
- package/src/popover/index.ts +3 -0
- package/src/popover/schema.d.ts +1 -0
- package/src/popover/schema.js +3 -0
- package/src/popover/schema.js.map +1 -0
- package/src/popover/schema.json +29 -0
- package/src/popover/schema.ts +1 -0
- package/src/progress-bar/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/radio-group/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/select/files/__name@dasherize__/__name@dasherize__.css.template +92 -0
- package/src/select/files/__name@dasherize__/__name@dasherize__.html.template +39 -0
- package/src/select/files/__name@dasherize__/__name@dasherize__.ts.template +51 -0
- package/src/select/index.d.ts +1 -0
- package/src/select/index.js +6 -0
- package/src/select/index.js.map +1 -0
- package/src/select/index.ts +3 -0
- package/src/select/schema.d.ts +1 -0
- package/src/select/schema.js +3 -0
- package/src/select/schema.js.map +1 -0
- package/src/select/schema.json +29 -0
- package/src/select/schema.ts +1 -0
- package/src/skeleton/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/skip-link/files/__name@dasherize__/__name@dasherize__.html.template +7 -0
- package/src/slider/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/spinbutton/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/spinner/files/__name@dasherize__/__name@dasherize__.css.template +85 -0
- package/src/spinner/files/__name@dasherize__/__name@dasherize__.html.template +25 -0
- package/src/spinner/files/__name@dasherize__/__name@dasherize__.ts.template +33 -0
- package/src/spinner/index.d.ts +1 -0
- package/src/spinner/index.js +6 -0
- package/src/spinner/index.js.map +1 -0
- package/src/spinner/index.ts +3 -0
- package/src/spinner/schema.d.ts +1 -0
- package/src/spinner/schema.js +3 -0
- package/src/spinner/schema.js.map +1 -0
- package/src/spinner/schema.json +29 -0
- package/src/spinner/schema.ts +1 -0
- package/src/switch/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/table/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/textarea/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/toast/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/toolbar/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/tooltip/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/tree-view/files/__name@dasherize__/__name@dasherize__.html.template +6 -0
- package/src/utils/generate-component.d.ts +1 -1
- package/src/utils/generate-component.js +1 -1
- package/src/utils/generate-component.ts +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "SemmetAngularNavbar",
|
|
4
|
+
"title": "Semmet Angular Navbar Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"description": "Generates a standalone Angular Navbar component with semantic navigation, responsive disclosure toggle, and current-page state.",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The name for the new 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 navbar component?"
|
|
14
|
+
},
|
|
15
|
+
"path": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"format": "path",
|
|
18
|
+
"$default": { "$source": "workingDirectory" },
|
|
19
|
+
"description": "The path where the component files should be created, relative to the workspace root. Defaults to the project's source root.",
|
|
20
|
+
"visible": false
|
|
21
|
+
},
|
|
22
|
+
"project": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "The name of the project to add the component to.",
|
|
25
|
+
"$default": { "$source": "projectName" }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["name", "project"]
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComponentSchematicOptions as Schema } from '../utils/schema-types';
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
<!-- Semmet Angular: Navigation Menu (Disclosure) — https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/ -->
|
|
2
|
+
<!--
|
|
3
|
+
Usage from a parent component template:
|
|
4
|
+
Import <%= classify(name) %> in that parent component.
|
|
5
|
+
|
|
6
|
+
<<%= selector %> />
|
|
7
|
+
-->
|
|
2
8
|
<!-- Deliberately avoids the ARIA menu/menuitem roles — APG reserves those for app-style menus. -->
|
|
3
9
|
<nav aria-label="Main">
|
|
4
10
|
<ul>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--semmet-color-primary: #18181b;
|
|
3
|
+
--semmet-color-on-primary: #ffffff;
|
|
4
|
+
--semmet-color-surface: #ffffff;
|
|
5
|
+
--semmet-color-backdrop: rgba(24, 24, 27, 0.44);
|
|
6
|
+
--semmet-color-outline: #d4d4d8;
|
|
7
|
+
--semmet-color-on-surface: #27272a;
|
|
8
|
+
--semmet-color-on-surface-variant: #71717a;
|
|
9
|
+
--semmet-radius-md: 10px;
|
|
10
|
+
--semmet-elevation-3: 0 20px 48px rgba(20, 24, 23, 0.22), 0 8px 20px rgba(20, 24, 23, 0.14);
|
|
11
|
+
--semmet-motion: 220ms cubic-bezier(0.2, 0, 0, 1);
|
|
12
|
+
|
|
13
|
+
display: inline-block;
|
|
14
|
+
font-family: Roboto, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.semmet-offcanvas__trigger {
|
|
18
|
+
min-height: 2.5rem;
|
|
19
|
+
padding: 0.5rem 1rem;
|
|
20
|
+
border: 1px solid transparent;
|
|
21
|
+
border-radius: var(--semmet-radius-md);
|
|
22
|
+
color: var(--semmet-color-on-primary);
|
|
23
|
+
background: var(--semmet-color-primary);
|
|
24
|
+
font: inherit;
|
|
25
|
+
font-weight: 650;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.semmet-offcanvas__trigger:focus-visible,
|
|
30
|
+
.semmet-offcanvas__close:focus-visible {
|
|
31
|
+
outline: 2px solid var(--semmet-color-primary);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.semmet-offcanvas__backdrop {
|
|
36
|
+
position: fixed;
|
|
37
|
+
inset: 0;
|
|
38
|
+
z-index: 1000;
|
|
39
|
+
background: var(--semmet-color-backdrop);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.semmet-offcanvas__panel {
|
|
43
|
+
position: fixed;
|
|
44
|
+
z-index: 1001;
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-rows: auto 1fr;
|
|
47
|
+
color: var(--semmet-color-on-surface);
|
|
48
|
+
background: var(--semmet-color-surface);
|
|
49
|
+
box-shadow: var(--semmet-elevation-3);
|
|
50
|
+
animation: semmet-offcanvas-enter var(--semmet-motion);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.semmet-offcanvas__panel--start,
|
|
54
|
+
.semmet-offcanvas__panel--end {
|
|
55
|
+
top: 0;
|
|
56
|
+
bottom: 0;
|
|
57
|
+
width: min(26rem, 92vw);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.semmet-offcanvas__panel--start {
|
|
61
|
+
left: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.semmet-offcanvas__panel--end {
|
|
65
|
+
right: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.semmet-offcanvas__panel--top,
|
|
69
|
+
.semmet-offcanvas__panel--bottom {
|
|
70
|
+
left: 0;
|
|
71
|
+
right: 0;
|
|
72
|
+
max-height: min(32rem, 82vh);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.semmet-offcanvas__panel--top {
|
|
76
|
+
top: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.semmet-offcanvas__panel--bottom {
|
|
80
|
+
bottom: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.semmet-offcanvas__header {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: start;
|
|
86
|
+
justify-content: space-between;
|
|
87
|
+
gap: 1rem;
|
|
88
|
+
padding: 1.25rem;
|
|
89
|
+
border-block-end: 1px solid var(--semmet-color-outline);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.semmet-offcanvas__title,
|
|
93
|
+
.semmet-offcanvas__description,
|
|
94
|
+
.semmet-offcanvas__placeholder {
|
|
95
|
+
margin: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.semmet-offcanvas__title {
|
|
99
|
+
font-size: 1.125rem;
|
|
100
|
+
line-height: 1.25;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.semmet-offcanvas__description {
|
|
104
|
+
margin-block-start: 0.375rem;
|
|
105
|
+
color: var(--semmet-color-on-surface-variant);
|
|
106
|
+
line-height: 1.4;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.semmet-offcanvas__close {
|
|
110
|
+
position: relative;
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
flex: 0 0 auto;
|
|
115
|
+
width: 2.5rem;
|
|
116
|
+
height: 2.5rem;
|
|
117
|
+
border: 0;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
color: inherit;
|
|
120
|
+
background: transparent;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.semmet-offcanvas__close:hover {
|
|
125
|
+
background: #f4f4f5;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.semmet-offcanvas__close span,
|
|
129
|
+
.semmet-offcanvas__close span::before,
|
|
130
|
+
.semmet-offcanvas__close span::after {
|
|
131
|
+
display: block;
|
|
132
|
+
width: 1rem;
|
|
133
|
+
height: 0.125rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.semmet-offcanvas__close span {
|
|
137
|
+
position: relative;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.semmet-offcanvas__close span::before,
|
|
141
|
+
.semmet-offcanvas__close span::after {
|
|
142
|
+
position: absolute;
|
|
143
|
+
top: 50%;
|
|
144
|
+
left: 0;
|
|
145
|
+
border-radius: 999px;
|
|
146
|
+
background: currentColor;
|
|
147
|
+
content: '';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.semmet-offcanvas__close span::before {
|
|
151
|
+
transform: rotate(45deg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.semmet-offcanvas__close span::after {
|
|
155
|
+
transform: rotate(-45deg);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.semmet-offcanvas__body {
|
|
159
|
+
min-height: 0;
|
|
160
|
+
overflow: auto;
|
|
161
|
+
padding: 1.25rem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.semmet-offcanvas__placeholder {
|
|
165
|
+
color: var(--semmet-color-on-surface-variant);
|
|
166
|
+
line-height: 1.5;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@keyframes semmet-offcanvas-enter {
|
|
170
|
+
from {
|
|
171
|
+
opacity: 0;
|
|
172
|
+
transform: translateX(1rem);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@media (prefers-reduced-motion: reduce) {
|
|
177
|
+
.semmet-offcanvas__panel {
|
|
178
|
+
animation-duration: 1ms;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!-- Semmet Angular: Offcanvas - modal drawer with dialog semantics and focus management. -->
|
|
2
|
+
<!--
|
|
3
|
+
Usage from a parent component template:
|
|
4
|
+
Import <%= classify(name) %> in that parent component.
|
|
5
|
+
|
|
6
|
+
<<%= selector %> triggerLabel="Open filters" title="Filters">
|
|
7
|
+
<app-filter-panel />
|
|
8
|
+
</<%= selector %>>
|
|
9
|
+
-->
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
class="semmet-offcanvas__trigger"
|
|
13
|
+
[attr.aria-controls]="instanceId + '-panel'"
|
|
14
|
+
[attr.aria-expanded]="isOpen()"
|
|
15
|
+
(click)="open($event)"
|
|
16
|
+
>
|
|
17
|
+
{{ triggerLabel() }}
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
@if (isOpen()) {
|
|
21
|
+
@if (backdrop()) {
|
|
22
|
+
<div class="semmet-offcanvas__backdrop" (click)="handleBackdropClick()"></div>
|
|
23
|
+
}
|
|
24
|
+
<section
|
|
25
|
+
#panel
|
|
26
|
+
tabindex="-1"
|
|
27
|
+
role="dialog"
|
|
28
|
+
aria-modal="true"
|
|
29
|
+
[id]="instanceId + '-panel'"
|
|
30
|
+
[class]="panelClasses()"
|
|
31
|
+
[attr.aria-labelledby]="instanceId + '-title'"
|
|
32
|
+
[attr.aria-describedby]="description() ? instanceId + '-description' : null"
|
|
33
|
+
(keydown)="handlePanelKeydown($event)"
|
|
34
|
+
>
|
|
35
|
+
<header class="semmet-offcanvas__header">
|
|
36
|
+
<div>
|
|
37
|
+
<h2 class="semmet-offcanvas__title" [id]="instanceId + '-title'">{{ title() }}</h2>
|
|
38
|
+
@if (description()) {
|
|
39
|
+
<p class="semmet-offcanvas__description" [id]="instanceId + '-description'">
|
|
40
|
+
{{ description() }}
|
|
41
|
+
</p>
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
class="semmet-offcanvas__close"
|
|
47
|
+
[attr.aria-label]="closeLabel()"
|
|
48
|
+
(click)="close()"
|
|
49
|
+
>
|
|
50
|
+
<span aria-hidden="true"></span>
|
|
51
|
+
</button>
|
|
52
|
+
</header>
|
|
53
|
+
<div class="semmet-offcanvas__body">
|
|
54
|
+
<ng-content></ng-content>
|
|
55
|
+
<p class="semmet-offcanvas__placeholder">
|
|
56
|
+
Replace this with filters, navigation, or contextual controls.
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Component, ElementRef, Injector, afterNextRender, booleanAttribute, computed, inject, input, signal, viewChild } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
const FOCUSABLE_SELECTOR =
|
|
4
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
5
|
+
|
|
6
|
+
type <%= classify(name) %>Placement = 'start' | 'end' | 'top' | 'bottom';
|
|
7
|
+
|
|
8
|
+
let nextId = 0;
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: '<%= selector %>',
|
|
12
|
+
imports: [],
|
|
13
|
+
templateUrl: './<%= dasherize(name) %>.html',
|
|
14
|
+
styleUrl: './<%= dasherize(name) %>.css',
|
|
15
|
+
})
|
|
16
|
+
export class <%= classify(name) %> {
|
|
17
|
+
private readonly injector = inject(Injector);
|
|
18
|
+
private readonly panelRef = viewChild<ElementRef<HTMLElement>>('panel');
|
|
19
|
+
private triggerElement: HTMLElement | null = null;
|
|
20
|
+
|
|
21
|
+
protected readonly instanceId = `<%= dasherize(name) %>-${nextId++}`;
|
|
22
|
+
protected readonly isOpen = signal(false);
|
|
23
|
+
|
|
24
|
+
readonly triggerLabel = input('Open panel');
|
|
25
|
+
readonly title = input('Panel title');
|
|
26
|
+
readonly description = input('Use this panel for secondary actions, filters, or contextual content.');
|
|
27
|
+
readonly closeLabel = input('Close panel');
|
|
28
|
+
readonly placement = input<<%= classify(name) %>Placement>('end');
|
|
29
|
+
readonly backdrop = input(true, { transform: booleanAttribute });
|
|
30
|
+
readonly closeOnBackdrop = input(true, { transform: booleanAttribute });
|
|
31
|
+
|
|
32
|
+
protected readonly panelClasses = computed(() => [
|
|
33
|
+
'semmet-offcanvas__panel',
|
|
34
|
+
`semmet-offcanvas__panel--${this.placement()}`,
|
|
35
|
+
].join(' '));
|
|
36
|
+
|
|
37
|
+
protected open(event: MouseEvent): void {
|
|
38
|
+
this.triggerElement = event.currentTarget as HTMLElement;
|
|
39
|
+
this.isOpen.set(true);
|
|
40
|
+
afterNextRender(() => this.focusFirstElement(), { injector: this.injector });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected close(): void {
|
|
44
|
+
this.isOpen.set(false);
|
|
45
|
+
this.triggerElement?.focus();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected handleBackdropClick(): void {
|
|
49
|
+
if (this.closeOnBackdrop()) {
|
|
50
|
+
this.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected handlePanelKeydown(event: KeyboardEvent): void {
|
|
55
|
+
if (event.key === 'Escape') {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
this.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (event.key === 'Tab') {
|
|
62
|
+
this.trapFocus(event);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private focusFirstElement(): void {
|
|
67
|
+
const panel = this.panelRef()?.nativeElement;
|
|
68
|
+
const firstFocusable = panel?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR);
|
|
69
|
+
(firstFocusable ?? panel)?.focus();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Keeps Tab/Shift+Tab cycling within the offcanvas while it behaves as a modal dialog.
|
|
73
|
+
private trapFocus(event: KeyboardEvent): void {
|
|
74
|
+
const panel = this.panelRef()?.nativeElement;
|
|
75
|
+
if (!panel) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
80
|
+
if (focusable.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const first = focusable[0];
|
|
85
|
+
const last = focusable[focusable.length - 1];
|
|
86
|
+
const active = panel.ownerDocument.activeElement;
|
|
87
|
+
|
|
88
|
+
if (event.shiftKey && active === first) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
last.focus();
|
|
91
|
+
} else if (!event.shiftKey && active === last) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
first.focus();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const offcanvas: (options: import("./schema").Schema) => import("@angular-devkit/schematics").Rule;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.offcanvas = void 0;
|
|
4
|
+
const generate_component_1 = require("../utils/generate-component");
|
|
5
|
+
exports.offcanvas = (0, generate_component_1.createAriaComponentSchematic)();
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,oEAA2E;AAE9D,QAAA,SAAS,GAAG,IAAA,iDAA4B,GAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComponentSchematicOptions as Schema } from '../utils/schema-types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "SemmetAngularOffcanvas",
|
|
4
|
+
"title": "Semmet Angular Offcanvas Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"description": "Generates a standalone Angular Offcanvas component using modal dialog semantics, focus trap, Escape close, and focus restoration.",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The name for the new 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 offcanvas component?"
|
|
14
|
+
},
|
|
15
|
+
"path": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"format": "path",
|
|
18
|
+
"$default": { "$source": "workingDirectory" },
|
|
19
|
+
"description": "The path where the component files should be created, relative to the workspace root. Defaults to the project's source root.",
|
|
20
|
+
"visible": false
|
|
21
|
+
},
|
|
22
|
+
"project": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "The name of the project to add the component to.",
|
|
25
|
+
"$default": { "$source": "projectName" }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["name", "project"]
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComponentSchematicOptions as Schema } from '../utils/schema-types';
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
<!-- Semmet Angular: Pagination — https://www.w3.org/TR/wai-aria-1.2/#aria-current -->
|
|
2
|
+
<!--
|
|
3
|
+
Usage from a parent component template:
|
|
4
|
+
Import <%= classify(name) %> in that parent component.
|
|
5
|
+
|
|
6
|
+
<<%= selector %> />
|
|
7
|
+
-->
|
|
2
8
|
<nav aria-label="Pagination">
|
|
3
9
|
<ul>
|
|
4
10
|
<li>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--semmet-color-primary: #18181b;
|
|
3
|
+
--semmet-color-on-primary: #ffffff;
|
|
4
|
+
--semmet-color-surface: #ffffff;
|
|
5
|
+
--semmet-color-surface-muted: #f4f4f5;
|
|
6
|
+
--semmet-color-outline: #d4d4d8;
|
|
7
|
+
--semmet-color-on-surface: #27272a;
|
|
8
|
+
--semmet-color-on-surface-variant: #71717a;
|
|
9
|
+
--semmet-radius-md: 10px;
|
|
10
|
+
--semmet-elevation-2: 0 12px 28px rgba(20, 24, 23, 0.16), 0 4px 12px rgba(20, 24, 23, 0.1);
|
|
11
|
+
--semmet-motion: 160ms cubic-bezier(0.2, 0, 0, 1);
|
|
12
|
+
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
font-family: Roboto, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.semmet-popover__trigger {
|
|
19
|
+
min-height: 2.5rem;
|
|
20
|
+
padding: 0.5rem 1rem;
|
|
21
|
+
border: 1px solid var(--semmet-color-outline);
|
|
22
|
+
border-radius: var(--semmet-radius-md);
|
|
23
|
+
color: var(--semmet-color-on-surface);
|
|
24
|
+
background: var(--semmet-color-surface);
|
|
25
|
+
font: inherit;
|
|
26
|
+
font-weight: 650;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.semmet-popover__trigger:focus-visible,
|
|
31
|
+
.semmet-popover__close:focus-visible,
|
|
32
|
+
.semmet-popover__panel:focus-visible {
|
|
33
|
+
outline: 2px solid var(--semmet-color-primary);
|
|
34
|
+
outline-offset: 2px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.semmet-popover__panel {
|
|
38
|
+
position: absolute;
|
|
39
|
+
z-index: 20;
|
|
40
|
+
width: min(20rem, calc(100vw - 2rem));
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
border: 1px solid var(--semmet-color-outline);
|
|
43
|
+
border-radius: var(--semmet-radius-md);
|
|
44
|
+
color: var(--semmet-color-on-surface);
|
|
45
|
+
background: var(--semmet-color-surface);
|
|
46
|
+
box-shadow: var(--semmet-elevation-2);
|
|
47
|
+
animation: semmet-popover-enter var(--semmet-motion);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.semmet-popover__panel--top {
|
|
51
|
+
left: 50%;
|
|
52
|
+
bottom: calc(100% + 0.5rem);
|
|
53
|
+
transform: translateX(-50%);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.semmet-popover__panel--right {
|
|
57
|
+
top: 50%;
|
|
58
|
+
left: calc(100% + 0.5rem);
|
|
59
|
+
transform: translateY(-50%);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.semmet-popover__panel--bottom {
|
|
63
|
+
top: calc(100% + 0.5rem);
|
|
64
|
+
left: 50%;
|
|
65
|
+
transform: translateX(-50%);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.semmet-popover__panel--left {
|
|
69
|
+
top: 50%;
|
|
70
|
+
right: calc(100% + 0.5rem);
|
|
71
|
+
transform: translateY(-50%);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.semmet-popover__header {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: start;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
gap: 1rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.semmet-popover__title,
|
|
82
|
+
.semmet-popover__body p {
|
|
83
|
+
margin: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.semmet-popover__title {
|
|
87
|
+
font-size: 1rem;
|
|
88
|
+
line-height: 1.3;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.semmet-popover__body {
|
|
92
|
+
margin-block-start: 0.625rem;
|
|
93
|
+
color: var(--semmet-color-on-surface-variant);
|
|
94
|
+
line-height: 1.5;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.semmet-popover__close {
|
|
98
|
+
position: relative;
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
flex: 0 0 auto;
|
|
103
|
+
width: 2rem;
|
|
104
|
+
height: 2rem;
|
|
105
|
+
border: 0;
|
|
106
|
+
border-radius: 999px;
|
|
107
|
+
color: inherit;
|
|
108
|
+
background: transparent;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.semmet-popover__close:hover {
|
|
113
|
+
background: var(--semmet-color-surface-muted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.semmet-popover__close span,
|
|
117
|
+
.semmet-popover__close span::before,
|
|
118
|
+
.semmet-popover__close span::after {
|
|
119
|
+
display: block;
|
|
120
|
+
width: 0.875rem;
|
|
121
|
+
height: 0.125rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.semmet-popover__close span {
|
|
125
|
+
position: relative;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.semmet-popover__close span::before,
|
|
129
|
+
.semmet-popover__close span::after {
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: 50%;
|
|
132
|
+
left: 0;
|
|
133
|
+
border-radius: 999px;
|
|
134
|
+
background: currentColor;
|
|
135
|
+
content: '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.semmet-popover__close span::before {
|
|
139
|
+
transform: rotate(45deg);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.semmet-popover__close span::after {
|
|
143
|
+
transform: rotate(-45deg);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@keyframes semmet-popover-enter {
|
|
147
|
+
from {
|
|
148
|
+
opacity: 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@media (prefers-reduced-motion: reduce) {
|
|
153
|
+
.semmet-popover__panel {
|
|
154
|
+
animation-duration: 1ms;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!-- Semmet Angular: Popover - disclosure trigger for short, contextual non-modal content. -->
|
|
2
|
+
<!--
|
|
3
|
+
Usage from a parent component template:
|
|
4
|
+
Import <%= classify(name) %> in that parent component.
|
|
5
|
+
|
|
6
|
+
<<%= selector %> triggerLabel="More information" title="Helpful context">
|
|
7
|
+
<a routerLink="/help">Read the guide</a>
|
|
8
|
+
</<%= selector %>>
|
|
9
|
+
-->
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
class="semmet-popover__trigger"
|
|
13
|
+
aria-haspopup="dialog"
|
|
14
|
+
[attr.aria-expanded]="isOpen()"
|
|
15
|
+
[attr.aria-controls]="instanceId + '-panel'"
|
|
16
|
+
(click)="toggle()"
|
|
17
|
+
(keydown)="handleKeydown($event)"
|
|
18
|
+
>
|
|
19
|
+
{{ triggerLabel() }}
|
|
20
|
+
</button>
|
|
21
|
+
|
|
22
|
+
@if (isOpen()) {
|
|
23
|
+
<section
|
|
24
|
+
#popover
|
|
25
|
+
tabindex="-1"
|
|
26
|
+
role="dialog"
|
|
27
|
+
[id]="instanceId + '-panel'"
|
|
28
|
+
[class]="popoverClasses()"
|
|
29
|
+
[attr.aria-labelledby]="instanceId + '-title'"
|
|
30
|
+
(keydown)="handleKeydown($event)"
|
|
31
|
+
>
|
|
32
|
+
<div class="semmet-popover__header">
|
|
33
|
+
<h2 class="semmet-popover__title" [id]="instanceId + '-title'">{{ title() }}</h2>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
class="semmet-popover__close"
|
|
37
|
+
[attr.aria-label]="closeLabel()"
|
|
38
|
+
(click)="close()"
|
|
39
|
+
>
|
|
40
|
+
<span aria-hidden="true"></span>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="semmet-popover__body">
|
|
44
|
+
<p>{{ content() }}</p>
|
|
45
|
+
<ng-content></ng-content>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
}
|