semmet-angular 0.9.0 → 0.11.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 -1
- package/package.json +1 -1
- package/src/collection.json +5 -0
- package/src/skeleton/files/__name@dasherize__/__name@dasherize__.css.template +91 -0
- package/src/skeleton/files/__name@dasherize__/__name@dasherize__.html.template +32 -0
- package/src/skeleton/files/__name@dasherize__/__name@dasherize__.ts.template +69 -0
- package/src/skeleton/index.d.ts +1 -0
- package/src/skeleton/index.js +6 -0
- package/src/skeleton/index.js.map +1 -0
- package/src/skeleton/index.ts +3 -0
- package/src/skeleton/schema.d.ts +1 -0
- package/src/skeleton/schema.js +3 -0
- package/src/skeleton/schema.js.map +1 -0
- package/src/skeleton/schema.json +29 -0
- package/src/skeleton/schema.ts +1 -0
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ The same pattern applies to `tabs`, `carousel`, and `disclosure`:
|
|
|
79
79
|
|
|
80
80
|
By default, projected panel/slide content is preserved after its first render. Set `[preserveContent]="false"` on the generated container to destroy inactive content when it closes or becomes inactive.
|
|
81
81
|
|
|
82
|
-
## Schematics (
|
|
82
|
+
## Schematics (33)
|
|
83
83
|
|
|
84
84
|
**Overlays & disclosure**
|
|
85
85
|
|
|
@@ -131,6 +131,7 @@ By default, projected panel/slide content is preserved after its first render. S
|
|
|
131
131
|
|---|---|---|
|
|
132
132
|
| `ng generate semmet-angular:alert <name>` | [Alert](https://www.w3.org/WAI/ARIA/apg/patterns/alert/) | Dismissible assertive live region |
|
|
133
133
|
| `ng generate semmet-angular:toast <name>` | Toast / Notification | Auto-dismisses after 5s (cleaned up via `DestroyRef`); dismissible early |
|
|
134
|
+
| `ng generate semmet-angular:skeleton <name>` | Skeleton Loading | Composable loading placeholder for text, media, circles, cards, and custom blocks |
|
|
134
135
|
| `ng generate semmet-angular:carousel <name>` | [Carousel](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/) | Previous/Next controls; `aria-live="polite"` slide region |
|
|
135
136
|
|
|
136
137
|
## What every generated component looks like
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semmet-angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Angular schematics that generate real, ARIA-conformant standalone components (Accordion, Tabs, Dialog, Disclosure, Tooltip...) with signals-based state and keyboard interaction built in.",
|
|
5
5
|
"publisher": "danilodevsilva",
|
|
6
6
|
"license": "MIT",
|
package/src/collection.json
CHANGED
|
@@ -146,6 +146,11 @@
|
|
|
146
146
|
"factory": "./progress-bar/index#progressBar",
|
|
147
147
|
"schema": "./progress-bar/schema.json"
|
|
148
148
|
},
|
|
149
|
+
"skeleton": {
|
|
150
|
+
"description": "Generates a standalone Angular Skeleton loading placeholder component for composable loading states.",
|
|
151
|
+
"factory": "./skeleton/index#skeleton",
|
|
152
|
+
"schema": "./skeleton/schema.json"
|
|
153
|
+
},
|
|
149
154
|
"alert": {
|
|
150
155
|
"description": "Generates a standalone Angular Alert component following the W3C ARIA Authoring Practices Guide.",
|
|
151
156
|
"factory": "./alert/index#alert",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--semmet-color-surface: #ffffff;
|
|
3
|
+
--semmet-color-outline: #d4d4d8;
|
|
4
|
+
--semmet-color-skeleton: #e4e4e7;
|
|
5
|
+
--semmet-color-skeleton-highlight: #f4f4f5;
|
|
6
|
+
--semmet-radius-sm: 8px;
|
|
7
|
+
--semmet-radius-md: 12px;
|
|
8
|
+
|
|
9
|
+
display: block;
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.semmet-skeleton,
|
|
14
|
+
.semmet-skeleton__line {
|
|
15
|
+
position: relative;
|
|
16
|
+
display: block;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
background: var(--semmet-color-skeleton);
|
|
20
|
+
border-radius: var(--semmet-radius-sm);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.semmet-skeleton {
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 1rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.semmet-skeleton--circle {
|
|
29
|
+
width: 2.75rem;
|
|
30
|
+
height: 2.75rem;
|
|
31
|
+
border-radius: 999px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.semmet-skeleton--card {
|
|
35
|
+
min-height: 16rem;
|
|
36
|
+
border: 1px solid var(--semmet-color-outline);
|
|
37
|
+
border-radius: var(--semmet-radius-md);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.semmet-skeleton--media {
|
|
41
|
+
height: 12.5rem;
|
|
42
|
+
border-radius: var(--semmet-radius-md);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.semmet-skeleton-text {
|
|
46
|
+
display: grid;
|
|
47
|
+
width: 100%;
|
|
48
|
+
gap: 0.65rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.semmet-skeleton__line {
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 1rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.semmet-skeleton--animated::after,
|
|
57
|
+
.semmet-skeleton-text--animated .semmet-skeleton__line::after {
|
|
58
|
+
content: '';
|
|
59
|
+
position: absolute;
|
|
60
|
+
inset: 0;
|
|
61
|
+
transform: translateX(-100%);
|
|
62
|
+
background: linear-gradient(
|
|
63
|
+
90deg,
|
|
64
|
+
transparent,
|
|
65
|
+
var(--semmet-color-skeleton-highlight),
|
|
66
|
+
transparent
|
|
67
|
+
);
|
|
68
|
+
animation: semmet-skeleton-shimmer 1.35s ease-in-out infinite;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes semmet-skeleton-shimmer {
|
|
72
|
+
100% {
|
|
73
|
+
transform: translateX(100%);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-color-scheme: dark) {
|
|
78
|
+
:host {
|
|
79
|
+
--semmet-color-surface: #18181b;
|
|
80
|
+
--semmet-color-outline: #3f3f46;
|
|
81
|
+
--semmet-color-skeleton: #27272a;
|
|
82
|
+
--semmet-color-skeleton-highlight: #3f3f46;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-reduced-motion: reduce) {
|
|
87
|
+
.semmet-skeleton--animated::after,
|
|
88
|
+
.semmet-skeleton-text--animated .semmet-skeleton__line::after {
|
|
89
|
+
animation: none;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!-- Semmet Angular: Skeleton Loading - decorative placeholder for pending content. -->
|
|
2
|
+
@if (variant() === 'text') {
|
|
3
|
+
<span
|
|
4
|
+
[id]="instanceId"
|
|
5
|
+
[class]="textClasses()"
|
|
6
|
+
[attr.aria-hidden]="ariaHidden()"
|
|
7
|
+
[attr.aria-label]="ariaLabel()"
|
|
8
|
+
[attr.aria-live]="ariaLive()"
|
|
9
|
+
[attr.role]="role()"
|
|
10
|
+
>
|
|
11
|
+
@for (line of lineItems(); track line) {
|
|
12
|
+
<span
|
|
13
|
+
class="semmet-skeleton__line"
|
|
14
|
+
[style.width]="lineWidth(line)"
|
|
15
|
+
[style.height]="heightStyle()"
|
|
16
|
+
[style.border-radius]="radiusStyle()"
|
|
17
|
+
></span>
|
|
18
|
+
}
|
|
19
|
+
</span>
|
|
20
|
+
} @else {
|
|
21
|
+
<span
|
|
22
|
+
[id]="instanceId"
|
|
23
|
+
[class]="skeletonClasses()"
|
|
24
|
+
[style.width]="widthStyle()"
|
|
25
|
+
[style.height]="heightStyle()"
|
|
26
|
+
[style.border-radius]="radiusStyle()"
|
|
27
|
+
[attr.aria-hidden]="ariaHidden()"
|
|
28
|
+
[attr.aria-label]="ariaLabel()"
|
|
29
|
+
[attr.aria-live]="ariaLive()"
|
|
30
|
+
[attr.role]="role()"
|
|
31
|
+
></span>
|
|
32
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Component, booleanAttribute, computed, input, numberAttribute } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
type <%= classify(name) %>Variant = 'rect' | 'text' | 'circle' | 'card' | 'media';
|
|
4
|
+
|
|
5
|
+
let nextId = 0;
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: '<%= selector %>',
|
|
9
|
+
imports: [],
|
|
10
|
+
templateUrl: './<%= dasherize(name) %>.html',
|
|
11
|
+
styleUrl: './<%= dasherize(name) %>.css',
|
|
12
|
+
})
|
|
13
|
+
export class <%= classify(name) %> {
|
|
14
|
+
protected readonly instanceId = `<%= dasherize(name) %>-${nextId++}`;
|
|
15
|
+
|
|
16
|
+
readonly variant = input<<%= classify(name) %>Variant>('rect');
|
|
17
|
+
readonly width = input<string | null>(null);
|
|
18
|
+
readonly height = input<string | null>(null);
|
|
19
|
+
readonly size = input<string | null>(null);
|
|
20
|
+
readonly radius = input<string | null>(null);
|
|
21
|
+
readonly lines = input(1, { transform: numberAttribute });
|
|
22
|
+
readonly animated = input(true, { transform: booleanAttribute });
|
|
23
|
+
readonly decorative = input(true, { transform: booleanAttribute });
|
|
24
|
+
readonly label = input('Loading content');
|
|
25
|
+
|
|
26
|
+
protected readonly normalizedLines = computed(() => Math.max(1, Math.floor(this.lines() || 1)));
|
|
27
|
+
protected readonly lineItems = computed(() =>
|
|
28
|
+
Array.from({ length: this.normalizedLines() }, (_item, index) => index),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
protected readonly skeletonClasses = computed(() =>
|
|
32
|
+
[
|
|
33
|
+
'semmet-skeleton',
|
|
34
|
+
`semmet-skeleton--${this.variant()}`,
|
|
35
|
+
this.animated() ? 'semmet-skeleton--animated' : '',
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' '),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
protected readonly textClasses = computed(() =>
|
|
42
|
+
[
|
|
43
|
+
'semmet-skeleton-text',
|
|
44
|
+
this.animated() ? 'semmet-skeleton-text--animated' : '',
|
|
45
|
+
]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(' '),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
protected readonly widthStyle = computed(() => this.size() ?? this.width());
|
|
51
|
+
protected readonly heightStyle = computed(() => this.size() ?? this.height());
|
|
52
|
+
protected readonly radiusStyle = computed(() => this.radius());
|
|
53
|
+
protected readonly ariaHidden = computed(() => (this.decorative() ? 'true' : null));
|
|
54
|
+
protected readonly role = computed(() => (this.decorative() ? null : 'status'));
|
|
55
|
+
protected readonly ariaLive = computed(() => (this.decorative() ? null : 'polite'));
|
|
56
|
+
protected readonly ariaLabel = computed(() => (this.decorative() ? null : this.label()));
|
|
57
|
+
|
|
58
|
+
protected lineWidth(index: number): string | null {
|
|
59
|
+
if (this.width()) {
|
|
60
|
+
return this.width();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.normalizedLines() > 1 && index === this.normalizedLines() - 1) {
|
|
64
|
+
return '68%';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const skeleton: (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.skeleton = void 0;
|
|
4
|
+
const generate_component_1 = require("../utils/generate-component");
|
|
5
|
+
exports.skeleton = (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,QAAQ,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": "SemmetAngularSkeleton",
|
|
4
|
+
"title": "Semmet Angular Skeleton Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"description": "Generates a standalone Angular Skeleton loading placeholder component for composable loading states.",
|
|
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 skeleton 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';
|