@frame-kit/ui-ng 0.0.1
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/COMPONENTS.md +683 -0
- package/DEVELOPMENT_GUIDE.md +1102 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/THEMING.md +130 -0
- package/core/headline/README.md +121 -0
- package/core/icon/README.md +173 -0
- package/core/image/README.md +210 -0
- package/core/link/README.md +297 -0
- package/core/separator/README.md +145 -0
- package/core/text/README.md +240 -0
- package/directives/infinite-scroll/README.md +102 -0
- package/directives/spotlight/README.md +154 -0
- package/directives/tooltip/README.md +147 -0
- package/docs/endpoint-link/README.md +142 -0
- package/docs/method-badge/README.md +154 -0
- package/fesm2022/frame-kit-ui-ng-core-headline.mjs +122 -0
- package/fesm2022/frame-kit-ui-ng-core-headline.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-icon.mjs +189 -0
- package/fesm2022/frame-kit-ui-ng-core-icon.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-image.mjs +123 -0
- package/fesm2022/frame-kit-ui-ng-core-image.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-link.mjs +369 -0
- package/fesm2022/frame-kit-ui-ng-core-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-separator.mjs +59 -0
- package/fesm2022/frame-kit-ui-ng-core-separator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-text.mjs +204 -0
- package/fesm2022/frame-kit-ui-ng-core-text.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs +74 -0
- package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs +76 -0
- package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs +425 -0
- package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs +63 -0
- package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs +43 -0
- package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-forms.mjs +3632 -0
- package/fesm2022/frame-kit-ui-ng-forms.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs +239 -0
- package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs +132 -0
- package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs +133 -0
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs +60 -0
- package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-toast.mjs +166 -0
- package/fesm2022/frame-kit-ui-ng-services-toast.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs +214 -0
- package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-alert.mjs +82 -0
- package/fesm2022/frame-kit-ui-ng-ui-alert.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs +76 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-badge.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs +68 -0
- package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-button.mjs +108 -0
- package/fesm2022/frame-kit-ui-ng-ui-button.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-callout.mjs +58 -0
- package/fesm2022/frame-kit-ui-ng-ui-callout.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-card.mjs +70 -0
- package/fesm2022/frame-kit-ui-ng-ui-card.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs +113 -0
- package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs +1288 -0
- package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs +456 -0
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs +398 -0
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs +398 -0
- package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs +125 -0
- package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs +113 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs +111 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs +103 -0
- package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs +135 -0
- package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-loader.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-loader.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs +79 -0
- package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs +40 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs +110 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs +91 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs +86 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs +443 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-note.mjs +56 -0
- package/fesm2022/frame-kit-ui-ng-ui-note.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs +105 -0
- package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs +110 -0
- package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs +129 -0
- package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs +42 -0
- package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs +894 -0
- package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-toast.mjs +179 -0
- package/fesm2022/frame-kit-ui-ng-ui-toast.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs +143 -0
- package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs +191 -0
- package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng.mjs +58 -0
- package/fesm2022/frame-kit-ui-ng.mjs.map +1 -0
- package/layouts/app-shell/README.md +357 -0
- package/layouts/content-split/README.md +180 -0
- package/package.json +253 -0
- package/services/overlay-orchestrator/README.md +184 -0
- package/services/spotlight/README.md +61 -0
- package/services/toast/README.md +118 -0
- package/types/frame-kit-ui-ng-core-headline.d.ts +38 -0
- package/types/frame-kit-ui-ng-core-icon.d.ts +74 -0
- package/types/frame-kit-ui-ng-core-image.d.ts +93 -0
- package/types/frame-kit-ui-ng-core-link.d.ts +251 -0
- package/types/frame-kit-ui-ng-core-separator.d.ts +28 -0
- package/types/frame-kit-ui-ng-core-text.d.ts +186 -0
- package/types/frame-kit-ui-ng-directives-infinite-scroll.d.ts +42 -0
- package/types/frame-kit-ui-ng-directives-spotlight.d.ts +51 -0
- package/types/frame-kit-ui-ng-directives-tooltip.d.ts +70 -0
- package/types/frame-kit-ui-ng-docs-endpoint-link.d.ts +43 -0
- package/types/frame-kit-ui-ng-docs-method-badge.d.ts +30 -0
- package/types/frame-kit-ui-ng-forms.d.ts +1674 -0
- package/types/frame-kit-ui-ng-layouts-app-shell.d.ts +75 -0
- package/types/frame-kit-ui-ng-layouts-content-split.d.ts +43 -0
- package/types/frame-kit-ui-ng-services-overlay-orchestrator.d.ts +96 -0
- package/types/frame-kit-ui-ng-services-spotlight.d.ts +32 -0
- package/types/frame-kit-ui-ng-services-toast.d.ts +100 -0
- package/types/frame-kit-ui-ng-ui-accordion.d.ts +86 -0
- package/types/frame-kit-ui-ng-ui-alert.d.ts +34 -0
- package/types/frame-kit-ui-ng-ui-avatar-stack.d.ts +38 -0
- package/types/frame-kit-ui-ng-ui-avatar.d.ts +36 -0
- package/types/frame-kit-ui-ng-ui-badge.d.ts +33 -0
- package/types/frame-kit-ui-ng-ui-breadcrumb.d.ts +45 -0
- package/types/frame-kit-ui-ng-ui-button.d.ts +48 -0
- package/types/frame-kit-ui-ng-ui-callout.d.ts +26 -0
- package/types/frame-kit-ui-ng-ui-card.d.ts +30 -0
- package/types/frame-kit-ui-ng-ui-copyable-field.d.ts +62 -0
- package/types/frame-kit-ui-ng-ui-data-table.d.ts +482 -0
- package/types/frame-kit-ui-ng-ui-dialog.d.ts +166 -0
- package/types/frame-kit-ui-ng-ui-drawer.d.ts +130 -0
- package/types/frame-kit-ui-ng-ui-dropdown-menu.d.ts +77 -0
- package/types/frame-kit-ui-ng-ui-editable-field.d.ts +65 -0
- package/types/frame-kit-ui-ng-ui-icon-badge.d.ts +45 -0
- package/types/frame-kit-ui-ng-ui-icon-list.d.ts +67 -0
- package/types/frame-kit-ui-ng-ui-inline-edit.d.ts +44 -0
- package/types/frame-kit-ui-ng-ui-list-editor.d.ts +56 -0
- package/types/frame-kit-ui-ng-ui-loader.d.ts +32 -0
- package/types/frame-kit-ui-ng-ui-menu-item.d.ts +27 -0
- package/types/frame-kit-ui-ng-ui-nav-brand.d.ts +25 -0
- package/types/frame-kit-ui-ng-ui-nav-group.d.ts +60 -0
- package/types/frame-kit-ui-ng-ui-nav-separator.d.ts +33 -0
- package/types/frame-kit-ui-ng-ui-node-tree-breadcrumb.d.ts +35 -0
- package/types/frame-kit-ui-ng-ui-node-tree.d.ts +135 -0
- package/types/frame-kit-ui-ng-ui-note.d.ts +22 -0
- package/types/frame-kit-ui-ng-ui-numbered-list.d.ts +52 -0
- package/types/frame-kit-ui-ng-ui-pagination.d.ts +49 -0
- package/types/frame-kit-ui-ng-ui-progress-bar.d.ts +50 -0
- package/types/frame-kit-ui-ng-ui-sidenav-link.d.ts +24 -0
- package/types/frame-kit-ui-ng-ui-tabs.d.ts +266 -0
- package/types/frame-kit-ui-ng-ui-timeline.d.ts +42 -0
- package/types/frame-kit-ui-ng-ui-toast.d.ts +56 -0
- package/types/frame-kit-ui-ng-ui-user-menu.d.ts +87 -0
- package/types/frame-kit-ui-ng-ui-wizard-dialog.d.ts +116 -0
- package/types/frame-kit-ui-ng.d.ts +53 -0
- package/ui/accordion/README.md +261 -0
- package/ui/alert/README.md +211 -0
- package/ui/avatar/README.md +167 -0
- package/ui/avatar-stack/README.md +164 -0
- package/ui/badge/README.md +162 -0
- package/ui/breadcrumb/README.md +240 -0
- package/ui/button/README.md +184 -0
- package/ui/callout/README.md +159 -0
- package/ui/card/README.md +174 -0
- package/ui/copyable-field/README.md +235 -0
- package/ui/data-table/README.md +408 -0
- package/ui/dialog/README.md +222 -0
- package/ui/drawer/README.md +274 -0
- package/ui/dropdown-menu/README.md +336 -0
- package/ui/editable-field/README.md +171 -0
- package/ui/icon-badge/README.md +131 -0
- package/ui/icon-list/README.md +205 -0
- package/ui/inline-edit/README.md +135 -0
- package/ui/list-editor/README.md +162 -0
- package/ui/loader/README.md +160 -0
- package/ui/menu-item/README.md +204 -0
- package/ui/nav-brand/README.md +111 -0
- package/ui/nav-group/README.md +145 -0
- package/ui/nav-separator/README.md +44 -0
- package/ui/node-tree/README.md +278 -0
- package/ui/node-tree-breadcrumb/README.md +164 -0
- package/ui/note/README.md +146 -0
- package/ui/numbered-list/README.md +187 -0
- package/ui/pagination/README.md +174 -0
- package/ui/progress-bar/README.md +223 -0
- package/ui/sidenav-link/README.md +214 -0
- package/ui/tabs/README.md +204 -0
- package/ui/timeline/README.md +285 -0
- package/ui/toast/README.md +243 -0
- package/ui/user-menu/README.md +260 -0
- package/ui/wizard-dialog/README.md +283 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# fk-endpoint-link
|
|
2
|
+
|
|
3
|
+
A sidebar row for an API endpoint in developer documentation. Combines an
|
|
4
|
+
`fk-method-badge` with a label inside a single clickable router link, and
|
|
5
|
+
tracks active state from the current URL.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
### Inputs
|
|
12
|
+
|
|
13
|
+
| Input | Type | Default | Description |
|
|
14
|
+
| ------------ | -------------------- | ------------ | ------------------------------------------------------- |
|
|
15
|
+
| `method` | `EndpointLinkMethod` | **required** | HTTP method shown in the badge |
|
|
16
|
+
| `label` | `string` | **required** | Endpoint description (e.g. operation summary) |
|
|
17
|
+
| `routerLink` | `string \| any[]` | **required** | Target route — passed through to Angular's `routerLink` |
|
|
18
|
+
| `exact` | `boolean` | `false` | Require exact URL match to apply the active state |
|
|
19
|
+
| `className` | `string` | `""` | Additional CSS classes merged onto the host element |
|
|
20
|
+
|
|
21
|
+
### Outputs
|
|
22
|
+
|
|
23
|
+
None.
|
|
24
|
+
|
|
25
|
+
### Content
|
|
26
|
+
|
|
27
|
+
`fk-endpoint-link` does not project content.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Active state tracked from `Router.url` via `NavigationEnd` (avoids known
|
|
34
|
+
`routerLinkActive` quirks with signal-based `[routerLink]` inputs)
|
|
35
|
+
- Prefix-matching by default so sub-paths still highlight; opt into exact
|
|
36
|
+
matching with `exact`
|
|
37
|
+
- Uses `fk-method-badge` so method colors and tokens stay consistent
|
|
38
|
+
- Ellipsis-truncated label keeps dense nav columns clean
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<fk-endpoint-link method="get" label="List users" [routerLink]="['/docs/api-reference', 'List_users']" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Import
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { EndpointLinkComponent } from '@frame-kit/ui-ng';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
@Component({
|
|
58
|
+
selector: 'app-example',
|
|
59
|
+
imports: [EndpointLinkComponent],
|
|
60
|
+
templateUrl: './example.component.html',
|
|
61
|
+
})
|
|
62
|
+
export class ExampleComponent {}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Selector
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<fk-endpoint-link method="get" label="..." [routerLink]="[...]" />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
### Verbs across methods
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<fk-endpoint-link method="get" label="List widgets" [routerLink]="['/list']" />
|
|
81
|
+
<fk-endpoint-link method="post" label="Create widget" [routerLink]="['/create']" />
|
|
82
|
+
<fk-endpoint-link method="delete" label="Delete widget" [routerLink]="['/delete']" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Exact URL matching
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<fk-endpoint-link method="get" label="Overview" [routerLink]="['/docs/api-reference']" [exact]="true" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Accessibility
|
|
94
|
+
|
|
95
|
+
- Renders as a semantic `<a>` element with a real `routerLink` target —
|
|
96
|
+
keyboard navigation works with Tab + Enter
|
|
97
|
+
- Focus ring uses `--fk-focus-ring` tokens
|
|
98
|
+
- Active state is communicated visually via color and font weight; the route
|
|
99
|
+
itself is the authoritative signal for assistive technology
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Design Tokens
|
|
104
|
+
|
|
105
|
+
```scss
|
|
106
|
+
--fk-endpoint-link-gap;
|
|
107
|
+
--fk-endpoint-link-padding-block;
|
|
108
|
+
--fk-endpoint-link-padding-inline;
|
|
109
|
+
--fk-endpoint-link-radius;
|
|
110
|
+
--fk-endpoint-link-font-size;
|
|
111
|
+
--fk-endpoint-link-color;
|
|
112
|
+
--fk-endpoint-link-bg-hover;
|
|
113
|
+
--fk-endpoint-link-bg-active;
|
|
114
|
+
--fk-endpoint-link-color-active;
|
|
115
|
+
--fk-endpoint-link-font-weight-active;
|
|
116
|
+
--fk-endpoint-link-focus-ring;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Override in your app stylesheet:
|
|
120
|
+
|
|
121
|
+
```scss
|
|
122
|
+
:root {
|
|
123
|
+
--fk-endpoint-link-bg-active: #dbeafe;
|
|
124
|
+
--fk-endpoint-link-color-active: #1e3a8a;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Behavior Notes
|
|
131
|
+
|
|
132
|
+
- Active detection compares the current URL (after redirects) against the
|
|
133
|
+
target derived from `routerLink`. String and array forms are both
|
|
134
|
+
supported
|
|
135
|
+
- Clicks call `stopPropagation` so the link doesn't re-trigger click
|
|
136
|
+
handlers on wrapping rows (useful when nested inside expandable groups)
|
|
137
|
+
- When rendered inside an `fk-app-shell`, clicks also call
|
|
138
|
+
`dismissSidenav()` on the ancestor shell. On mobile, this closes the
|
|
139
|
+
sidenav overlay so the user lands on the endpoint content; on desktop
|
|
140
|
+
it's a no-op because `dismissSidenav` only affects the mobile temporary
|
|
141
|
+
state. The ancestor shell is optional — the link works standalone too
|
|
142
|
+
- The label is truncated with ellipsis when it exceeds the available width
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# fk-method-badge
|
|
2
|
+
|
|
3
|
+
A token-driven HTTP method badge for developer documentation. Renders a
|
|
4
|
+
compact, colored label (`GET`, `POST`, `PUT`, etc.) so readers can scan an
|
|
5
|
+
endpoint list and identify verbs at a glance.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
### Inputs
|
|
12
|
+
|
|
13
|
+
| Input | Type | Default | Description |
|
|
14
|
+
| ----------- | ------------------- | ------------ | --------------------------------------------------- |
|
|
15
|
+
| `method` | `MethodBadgeMethod` | **required** | HTTP method (lowercase to match OpenAPI) |
|
|
16
|
+
| `className` | `string` | `""` | Additional CSS classes merged onto the host element |
|
|
17
|
+
| `id` | `string \| null` | `null` | Optional ID applied to the inner label span |
|
|
18
|
+
| `ariaLabel` | `string \| null` | `null` | Accessible label for the badge |
|
|
19
|
+
|
|
20
|
+
### Outputs
|
|
21
|
+
|
|
22
|
+
None.
|
|
23
|
+
|
|
24
|
+
### Content
|
|
25
|
+
|
|
26
|
+
`fk-method-badge` does not project content — the label is derived from
|
|
27
|
+
`method` and rendered in uppercase.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Per-method pastel color palette baked in as fallbacks
|
|
34
|
+
- Token-driven background and text colors with dark-mode overrides
|
|
35
|
+
- Monospace label for consistent width across methods
|
|
36
|
+
- Works standalone or inside other components (e.g. `fk-endpoint-link`)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<fk-method-badge method="get" />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<fk-method-badge method="post" />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Import
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { MethodBadgeComponent } from '@frame-kit/ui-ng';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
@Component({
|
|
60
|
+
selector: 'app-example',
|
|
61
|
+
imports: [MethodBadgeComponent],
|
|
62
|
+
templateUrl: './example.component.html',
|
|
63
|
+
})
|
|
64
|
+
export class ExampleComponent {}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Selector
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<fk-method-badge method="get" />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### All supported methods
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<fk-method-badge method="get" />
|
|
83
|
+
<fk-method-badge method="post" />
|
|
84
|
+
<fk-method-badge method="put" />
|
|
85
|
+
<fk-method-badge method="patch" />
|
|
86
|
+
<fk-method-badge method="delete" />
|
|
87
|
+
<fk-method-badge method="head" />
|
|
88
|
+
<fk-method-badge method="options" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Inside an endpoint list
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<fk-endpoint-link method="get" label="List users" [routerLink]="['/docs/api-reference', 'List_users']" />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Accessibility
|
|
100
|
+
|
|
101
|
+
- Renders as an inline span element inside the host
|
|
102
|
+
- Set `ariaLabel` for screen readers when the verb alone is not enough
|
|
103
|
+
context (e.g. `ariaLabel="HTTP GET"`)
|
|
104
|
+
- Color alone does not convey meaning — the verb is always present as text
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Design Tokens
|
|
109
|
+
|
|
110
|
+
```scss
|
|
111
|
+
--fk-method-badge-min-width;
|
|
112
|
+
--fk-method-badge-padding-block;
|
|
113
|
+
--fk-method-badge-padding-inline;
|
|
114
|
+
--fk-method-badge-radius;
|
|
115
|
+
--fk-method-badge-font-size;
|
|
116
|
+
--fk-method-badge-font-weight;
|
|
117
|
+
--fk-method-badge-color;
|
|
118
|
+
|
|
119
|
+
--fk-method-badge-bg-neutral;
|
|
120
|
+
--fk-method-badge-bg-get;
|
|
121
|
+
--fk-method-badge-bg-post;
|
|
122
|
+
--fk-method-badge-bg-put;
|
|
123
|
+
--fk-method-badge-bg-patch;
|
|
124
|
+
--fk-method-badge-bg-delete;
|
|
125
|
+
--fk-method-badge-bg-head;
|
|
126
|
+
--fk-method-badge-bg-options;
|
|
127
|
+
|
|
128
|
+
--fk-method-badge-color-get;
|
|
129
|
+
--fk-method-badge-color-post;
|
|
130
|
+
--fk-method-badge-color-put;
|
|
131
|
+
--fk-method-badge-color-patch;
|
|
132
|
+
--fk-method-badge-color-delete;
|
|
133
|
+
--fk-method-badge-color-head;
|
|
134
|
+
--fk-method-badge-color-options;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Override in your app stylesheet:
|
|
138
|
+
|
|
139
|
+
```scss
|
|
140
|
+
:root {
|
|
141
|
+
--fk-method-badge-radius: 9999px;
|
|
142
|
+
--fk-method-badge-bg-get: #a7f3d0;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Behavior Notes
|
|
149
|
+
|
|
150
|
+
- Method names are lowercase on input to match OpenAPI conventions; the
|
|
151
|
+
rendered label is automatically uppercased
|
|
152
|
+
- `head` and `options` fall back to the neutral background by default
|
|
153
|
+
- Typography is locked to a monospace stack so labels align vertically in
|
|
154
|
+
dense lists
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { input, viewChild, inject, Injector, computed, isDevMode, runInInjectionContext, afterNextRender, effect, HostBinding, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
// headline.component.ts
|
|
6
|
+
class HeadlineComponent {
|
|
7
|
+
// Inputs
|
|
8
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
9
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
10
|
+
/** Custom font-size override applied directly to the host element. */
|
|
11
|
+
fontSize = input(null, ...(ngDevMode ? [{ debugName: "fontSize" }] : /* istanbul ignore next */ []));
|
|
12
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
13
|
+
/** Semantic heading level rendered in the DOM (h1–h6). */
|
|
14
|
+
level = input(2, ...(ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []));
|
|
15
|
+
/** Visual style applied to the heading — `"muted"` reduces contrast. */
|
|
16
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
17
|
+
/** Hides the heading visually while keeping it in the accessibility tree. */
|
|
18
|
+
visuallyHidden = input(false, ...(ngDevMode ? [{ debugName: "visuallyHidden" }] : /* istanbul ignore next */ []));
|
|
19
|
+
// Template refs (signals)
|
|
20
|
+
h1 = viewChild.required('h1');
|
|
21
|
+
h2 = viewChild.required('h2');
|
|
22
|
+
h3 = viewChild.required('h3');
|
|
23
|
+
h4 = viewChild.required('h4');
|
|
24
|
+
h5 = viewChild.required('h5');
|
|
25
|
+
h6 = viewChild.required('h6');
|
|
26
|
+
injector = inject(Injector);
|
|
27
|
+
// Inject document (SSR-safe-ish if consumers provide it; still guard usage)
|
|
28
|
+
doc = inject(DOCUMENT, { optional: true });
|
|
29
|
+
// Host classes — single source of truth
|
|
30
|
+
classes = computed(() => {
|
|
31
|
+
return [
|
|
32
|
+
'fk-headline',
|
|
33
|
+
`fk-headline--h${this.level()}`,
|
|
34
|
+
this.variant() === 'muted' ? 'fk-headline--muted' : '',
|
|
35
|
+
this.visuallyHidden() ? 'sr-only' : '',
|
|
36
|
+
this.className(),
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ');
|
|
40
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
41
|
+
get hostClass() {
|
|
42
|
+
return this.classes();
|
|
43
|
+
}
|
|
44
|
+
get hostFontSize() {
|
|
45
|
+
return this.fontSize();
|
|
46
|
+
}
|
|
47
|
+
// Choose the correct heading template
|
|
48
|
+
template = computed(() => {
|
|
49
|
+
switch (this.level()) {
|
|
50
|
+
case 1:
|
|
51
|
+
return this.h1();
|
|
52
|
+
case 2:
|
|
53
|
+
return this.h2();
|
|
54
|
+
case 3:
|
|
55
|
+
return this.h3();
|
|
56
|
+
case 4:
|
|
57
|
+
return this.h4();
|
|
58
|
+
case 5:
|
|
59
|
+
return this.h5();
|
|
60
|
+
case 6:
|
|
61
|
+
return this.h6();
|
|
62
|
+
default:
|
|
63
|
+
return this.h2();
|
|
64
|
+
}
|
|
65
|
+
}, ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
|
|
66
|
+
// Dev-only: avoid spamming the console for the same count
|
|
67
|
+
static lastWarnedH1Count = null;
|
|
68
|
+
constructor() {
|
|
69
|
+
// Run once after first render, then re-check after render whenever `level()` changes.
|
|
70
|
+
// We schedule the DOM query after rendering to ensure this component’s heading exists.
|
|
71
|
+
const scheduleCheck = () => {
|
|
72
|
+
if (!isDevMode()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const doc = this.doc;
|
|
76
|
+
if (!doc) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// afterNextRender() requires injection context; effect() runs outside it
|
|
80
|
+
runInInjectionContext(this.injector, () => {
|
|
81
|
+
afterNextRender(() => {
|
|
82
|
+
if (this.level() !== 1) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const h1Count = doc.querySelectorAll('h1').length;
|
|
86
|
+
if (h1Count > 1 && HeadlineComponent.lastWarnedH1Count !== h1Count) {
|
|
87
|
+
HeadlineComponent.lastWarnedH1Count = h1Count;
|
|
88
|
+
console.warn(`[fk-headline] Detected ${h1Count} <h1> elements on the page. ` +
|
|
89
|
+
`Best practice is usually a single <h1> per page. ` +
|
|
90
|
+
`Consider lowering some headline levels to <h2>+ (or ensure only one top-level heading).`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
// initial
|
|
96
|
+
scheduleCheck();
|
|
97
|
+
// when level changes
|
|
98
|
+
effect(() => {
|
|
99
|
+
this.level();
|
|
100
|
+
scheduleCheck();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HeadlineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
104
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.9", type: HeadlineComponent, isStandalone: true, selector: "fk-headline", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, fontSize: { classPropertyName: "fontSize", publicName: "fontSize", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, visuallyHidden: { classPropertyName: "visuallyHidden", publicName: "visuallyHidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass", "style.font-size": "this.hostFontSize" } }, viewQueries: [{ propertyName: "h1", first: true, predicate: ["h1"], descendants: true, isSignal: true }, { propertyName: "h2", first: true, predicate: ["h2"], descendants: true, isSignal: true }, { propertyName: "h3", first: true, predicate: ["h3"], descendants: true, isSignal: true }, { propertyName: "h4", first: true, predicate: ["h4"], descendants: true, isSignal: true }, { propertyName: "h5", first: true, predicate: ["h5"], descendants: true, isSignal: true }, { propertyName: "h6", first: true, predicate: ["h6"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Stable projection point -->\n<ng-template #projected>\n <ng-content></ng-content>\n</ng-template>\n\n<!-- Render chosen heading template -->\n<ng-container\n [ngTemplateOutlet]=\"template()\"\n [ngTemplateOutletContext]=\"{ $implicit: projected }\"\n></ng-container>\n\n<!-- Heading templates -->\n<ng-template #h1 let-content>\n <h1 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h1>\n</ng-template>\n\n<ng-template #h2 let-content>\n <h2 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h2>\n</ng-template>\n\n<ng-template #h3 let-content>\n <h3 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h3>\n</ng-template>\n\n<ng-template #h4 let-content>\n <h4 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h4>\n</ng-template>\n\n<ng-template #h5 let-content>\n <h5 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h5>\n</ng-template>\n\n<ng-template #h6 let-content>\n <h6 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h6>\n</ng-template>\n", styles: [":host h1,:host h2,:host h3,:host h4,:host h5,:host h6{margin:0;font-size:inherit;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:var(--fk-headline-color-default, var(--fk-color-text, #1a1a1a))}:host{display:block;margin-block:var(--fk-headline-margin-block-start, var(--fk-rhythm-4, 1rem)) var(--fk-headline-margin-block-end, 0)}:host(:first-child){margin-block-start:0}:host.fk-headline--h1{font-size:var(--fk-typography-h1-font-size, 2.25rem);font-weight:var(--fk-typography-h1-font-weight, var(--fk-font-weight-bold, 700));line-height:var(--fk-typography-h1-line-height, 1.05);letter-spacing:var(--fk-typography-h1-letter-spacing, -.02em)}:host.fk-headline--h2{font-size:var(--fk-typography-h2-font-size, 1.75rem);font-weight:var(--fk-typography-h2-font-weight, var(--fk-font-weight-bold, 700));line-height:var(--fk-typography-h2-line-height, 1.1);letter-spacing:var(--fk-typography-h2-letter-spacing, -.01em)}:host.fk-headline--h3{font-size:var(--fk-typography-h3-font-size, 1.375rem);font-weight:var(--fk-typography-h3-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h3-line-height, 1.2);letter-spacing:var(--fk-typography-h3-letter-spacing, 0em)}:host.fk-headline--h4{font-size:var(--fk-typography-h4-font-size, 1.125rem);font-weight:var(--fk-typography-h4-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h4-line-height, 1.25);letter-spacing:var(--fk-typography-h4-letter-spacing, 0em)}:host.fk-headline--h5{font-size:var(--fk-typography-h5-font-size, .9375rem);font-weight:var(--fk-typography-h5-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h5-line-height, 1.3);letter-spacing:var(--fk-typography-h5-letter-spacing, .01em)}:host.fk-headline--h6{font-size:var(--fk-typography-h6-font-size, .8125rem);font-weight:var(--fk-typography-h6-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h6-line-height, 1.4);letter-spacing:var(--fk-typography-h6-letter-spacing, .02em)}:host.fk-headline--muted h1,:host.fk-headline--muted h2,:host.fk-headline--muted h3,:host.fk-headline--muted h4,:host.fk-headline--muted h5,:host.fk-headline--muted h6{color:var(--fk-headline-color-muted, var(--fk-color-muted, #6b7280))}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
105
|
+
}
|
|
106
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HeadlineComponent, decorators: [{
|
|
107
|
+
type: Component,
|
|
108
|
+
args: [{ selector: 'fk-headline', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Stable projection point -->\n<ng-template #projected>\n <ng-content></ng-content>\n</ng-template>\n\n<!-- Render chosen heading template -->\n<ng-container\n [ngTemplateOutlet]=\"template()\"\n [ngTemplateOutletContext]=\"{ $implicit: projected }\"\n></ng-container>\n\n<!-- Heading templates -->\n<ng-template #h1 let-content>\n <h1 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h1>\n</ng-template>\n\n<ng-template #h2 let-content>\n <h2 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h2>\n</ng-template>\n\n<ng-template #h3 let-content>\n <h3 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h3>\n</ng-template>\n\n<ng-template #h4 let-content>\n <h4 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h4>\n</ng-template>\n\n<ng-template #h5 let-content>\n <h5 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h5>\n</ng-template>\n\n<ng-template #h6 let-content>\n <h6 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h6>\n</ng-template>\n", styles: [":host h1,:host h2,:host h3,:host h4,:host h5,:host h6{margin:0;font-size:inherit;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:var(--fk-headline-color-default, var(--fk-color-text, #1a1a1a))}:host{display:block;margin-block:var(--fk-headline-margin-block-start, var(--fk-rhythm-4, 1rem)) var(--fk-headline-margin-block-end, 0)}:host(:first-child){margin-block-start:0}:host.fk-headline--h1{font-size:var(--fk-typography-h1-font-size, 2.25rem);font-weight:var(--fk-typography-h1-font-weight, var(--fk-font-weight-bold, 700));line-height:var(--fk-typography-h1-line-height, 1.05);letter-spacing:var(--fk-typography-h1-letter-spacing, -.02em)}:host.fk-headline--h2{font-size:var(--fk-typography-h2-font-size, 1.75rem);font-weight:var(--fk-typography-h2-font-weight, var(--fk-font-weight-bold, 700));line-height:var(--fk-typography-h2-line-height, 1.1);letter-spacing:var(--fk-typography-h2-letter-spacing, -.01em)}:host.fk-headline--h3{font-size:var(--fk-typography-h3-font-size, 1.375rem);font-weight:var(--fk-typography-h3-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h3-line-height, 1.2);letter-spacing:var(--fk-typography-h3-letter-spacing, 0em)}:host.fk-headline--h4{font-size:var(--fk-typography-h4-font-size, 1.125rem);font-weight:var(--fk-typography-h4-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h4-line-height, 1.25);letter-spacing:var(--fk-typography-h4-letter-spacing, 0em)}:host.fk-headline--h5{font-size:var(--fk-typography-h5-font-size, .9375rem);font-weight:var(--fk-typography-h5-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h5-line-height, 1.3);letter-spacing:var(--fk-typography-h5-letter-spacing, .01em)}:host.fk-headline--h6{font-size:var(--fk-typography-h6-font-size, .8125rem);font-weight:var(--fk-typography-h6-font-weight, var(--fk-font-weight-semibold, 600));line-height:var(--fk-typography-h6-line-height, 1.4);letter-spacing:var(--fk-typography-h6-letter-spacing, .02em)}:host.fk-headline--muted h1,:host.fk-headline--muted h2,:host.fk-headline--muted h3,:host.fk-headline--muted h4,:host.fk-headline--muted h5,:host.fk-headline--muted h6{color:var(--fk-headline-color-muted, var(--fk-color-muted, #6b7280))}\n"] }]
|
|
109
|
+
}], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], fontSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontSize", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], visuallyHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "visuallyHidden", required: false }] }], h1: [{ type: i0.ViewChild, args: ['h1', { isSignal: true }] }], h2: [{ type: i0.ViewChild, args: ['h2', { isSignal: true }] }], h3: [{ type: i0.ViewChild, args: ['h3', { isSignal: true }] }], h4: [{ type: i0.ViewChild, args: ['h4', { isSignal: true }] }], h5: [{ type: i0.ViewChild, args: ['h5', { isSignal: true }] }], h6: [{ type: i0.ViewChild, args: ['h6', { isSignal: true }] }], hostClass: [{
|
|
110
|
+
type: HostBinding,
|
|
111
|
+
args: ['class']
|
|
112
|
+
}], hostFontSize: [{
|
|
113
|
+
type: HostBinding,
|
|
114
|
+
args: ['style.font-size']
|
|
115
|
+
}] } });
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generated bundle index. Do not edit.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
export { HeadlineComponent };
|
|
122
|
+
//# sourceMappingURL=frame-kit-ui-ng-core-headline.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-kit-ui-ng-core-headline.mjs","sources":["../../../../packages/ui-ng/core/headline/headline.component.ts","../../../../packages/ui-ng/core/headline/headline.component.html","../../../../packages/ui-ng/core/headline/frame-kit-ui-ng-core-headline.ts"],"sourcesContent":["// headline.component.ts\nimport { DOCUMENT, NgTemplateOutlet } from '@angular/common';\nimport {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n HostBinding,\n inject,\n Injector,\n input,\n isDevMode,\n runInInjectionContext,\n TemplateRef,\n viewChild,\n} from '@angular/core';\n\nimport type { HeadlineLevel, HeadlineVariant } from './headline.types';\n\n@Component({\n selector: 'fk-headline',\n standalone: true,\n imports: [NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n styleUrl: './headline.component.scss',\n templateUrl: './headline.component.html',\n})\nexport class HeadlineComponent {\n // Inputs\n readonly ariaLabel = input<string | null>(null);\n readonly className = input<string>('');\n /** Custom font-size override applied directly to the host element. */\n readonly fontSize = input<string | null>(null);\n readonly id = input<string | null>(null);\n /** Semantic heading level rendered in the DOM (h1–h6). */\n readonly level = input<HeadlineLevel>(2);\n /** Visual style applied to the heading — `\"muted\"` reduces contrast. */\n readonly variant = input<HeadlineVariant>('default');\n /** Hides the heading visually while keeping it in the accessibility tree. */\n readonly visuallyHidden = input(false);\n\n // Template refs (signals)\n private readonly h1 = viewChild.required<TemplateRef<unknown>>('h1');\n private readonly h2 = viewChild.required<TemplateRef<unknown>>('h2');\n private readonly h3 = viewChild.required<TemplateRef<unknown>>('h3');\n private readonly h4 = viewChild.required<TemplateRef<unknown>>('h4');\n private readonly h5 = viewChild.required<TemplateRef<unknown>>('h5');\n private readonly h6 = viewChild.required<TemplateRef<unknown>>('h6');\n\n private readonly injector = inject(Injector);\n\n // Inject document (SSR-safe-ish if consumers provide it; still guard usage)\n private readonly doc = inject(DOCUMENT, { optional: true });\n\n // Host classes — single source of truth\n readonly classes = computed(() => {\n return [\n 'fk-headline',\n `fk-headline--h${this.level()}`,\n this.variant() === 'muted' ? 'fk-headline--muted' : '',\n this.visuallyHidden() ? 'sr-only' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n @HostBinding('class')\n get hostClass() {\n return this.classes();\n }\n\n @HostBinding('style.font-size')\n get hostFontSize(): string | null {\n return this.fontSize();\n }\n\n // Choose the correct heading template\n readonly template = computed(() => {\n switch (this.level()) {\n case 1:\n return this.h1();\n case 2:\n return this.h2();\n case 3:\n return this.h3();\n case 4:\n return this.h4();\n case 5:\n return this.h5();\n case 6:\n return this.h6();\n default:\n return this.h2();\n }\n });\n\n // Dev-only: avoid spamming the console for the same count\n private static lastWarnedH1Count: number | null = null;\n\n constructor() {\n // Run once after first render, then re-check after render whenever `level()` changes.\n // We schedule the DOM query after rendering to ensure this component’s heading exists.\n const scheduleCheck = () => {\n if (!isDevMode()) {\n return;\n }\n\n const doc = this.doc;\n\n if (!doc) {\n return;\n }\n\n // afterNextRender() requires injection context; effect() runs outside it\n runInInjectionContext(this.injector, () => {\n afterNextRender(() => {\n if (this.level() !== 1) {\n return;\n }\n\n const h1Count = doc.querySelectorAll('h1').length;\n\n if (h1Count > 1 && HeadlineComponent.lastWarnedH1Count !== h1Count) {\n HeadlineComponent.lastWarnedH1Count = h1Count;\n\n console.warn(\n `[fk-headline] Detected ${h1Count} <h1> elements on the page. ` +\n `Best practice is usually a single <h1> per page. ` +\n `Consider lowering some headline levels to <h2>+ (or ensure only one top-level heading).`,\n );\n }\n });\n });\n };\n\n // initial\n scheduleCheck();\n\n // when level changes\n effect(() => {\n this.level();\n\n scheduleCheck();\n });\n }\n}\n","<!-- Stable projection point -->\n<ng-template #projected>\n <ng-content></ng-content>\n</ng-template>\n\n<!-- Render chosen heading template -->\n<ng-container\n [ngTemplateOutlet]=\"template()\"\n [ngTemplateOutletContext]=\"{ $implicit: projected }\"\n></ng-container>\n\n<!-- Heading templates -->\n<ng-template #h1 let-content>\n <h1 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h1>\n</ng-template>\n\n<ng-template #h2 let-content>\n <h2 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h2>\n</ng-template>\n\n<ng-template #h3 let-content>\n <h3 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h3>\n</ng-template>\n\n<ng-template #h4 let-content>\n <h4 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h4>\n</ng-template>\n\n<ng-template #h5 let-content>\n <h5 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h5>\n</ng-template>\n\n<ng-template #h6 let-content>\n <h6 [id]=\"id() || null\" [attr.aria-label]=\"ariaLabel() || null\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </h6>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Injectable, input, computed, isDevMode, HostBinding, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
|
|
3
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
|
+
|
|
5
|
+
class IconRegistryService {
|
|
6
|
+
icons = new Map();
|
|
7
|
+
sanitizer = inject(DomSanitizer);
|
|
8
|
+
registerIcon(name, svgContent) {
|
|
9
|
+
const normalized = this.normalizeSvg(svgContent);
|
|
10
|
+
this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(normalized));
|
|
11
|
+
}
|
|
12
|
+
normalizeSvg(svg) {
|
|
13
|
+
const svgStart = svg.indexOf('<svg');
|
|
14
|
+
const svgOnly = svgStart > 0 ? svg.slice(svgStart) : svg;
|
|
15
|
+
return svgOnly
|
|
16
|
+
.replace(/<svg([^>]*)>/, (_match, attrs) => {
|
|
17
|
+
const cleaned = attrs.replace(/\s*(?:width|height)="[^"]*"/gi, '');
|
|
18
|
+
const hasFill = /\bfill\s*=/i.test(cleaned);
|
|
19
|
+
return `<svg${cleaned}${hasFill ? '' : ' fill="currentColor"'}>`;
|
|
20
|
+
})
|
|
21
|
+
.replace(/\b(stroke|fill)="(?!none|currentColor)[^"]*"/gi, '$1="currentColor"');
|
|
22
|
+
}
|
|
23
|
+
registerIcons(icons) {
|
|
24
|
+
for (const [name, svg] of Object.entries(icons)) {
|
|
25
|
+
this.registerIcon(name, svg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
getIcon(name) {
|
|
29
|
+
return this.icons.get(name) ?? null;
|
|
30
|
+
}
|
|
31
|
+
hasIcon(name) {
|
|
32
|
+
return this.icons.has(name);
|
|
33
|
+
}
|
|
34
|
+
getRegisteredNames() {
|
|
35
|
+
return Array.from(this.icons.keys());
|
|
36
|
+
}
|
|
37
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IconRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
38
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IconRegistryService, providedIn: 'root' });
|
|
39
|
+
}
|
|
40
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IconRegistryService, decorators: [{
|
|
41
|
+
type: Injectable,
|
|
42
|
+
args: [{ providedIn: 'root' }]
|
|
43
|
+
}] });
|
|
44
|
+
|
|
45
|
+
class IconComponent {
|
|
46
|
+
registry = inject(IconRegistryService);
|
|
47
|
+
// ===== INPUTS =====
|
|
48
|
+
/** Registry key for the SVG icon to render. */
|
|
49
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** Preset size token applied as a CSS class; pass a CSS value for custom sizes. */
|
|
51
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
52
|
+
/** Icon color — preset token names resolve to design-token colors, any other string is applied as a literal CSS `color`. */
|
|
53
|
+
color = input('inherit', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
54
|
+
// ===== BASE PROPS =====
|
|
55
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
56
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
57
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/** Explicitly sets `aria-hidden`; when null, the directive infers it from `ariaLabel`. */
|
|
59
|
+
ariaHidden = input(null, ...(ngDevMode ? [{ debugName: "ariaHidden" }] : /* istanbul ignore next */ []));
|
|
60
|
+
// ===== COMPUTED =====
|
|
61
|
+
svgContent = computed(() => {
|
|
62
|
+
const name = this.name();
|
|
63
|
+
// No name bound (e.g. an optional/absent icon) — render nothing, no warning.
|
|
64
|
+
if (!name) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const content = this.registry.getIcon(name);
|
|
68
|
+
if (!content && isDevMode()) {
|
|
69
|
+
console.warn(`[fk-icon] Icon "${name}" not found in registry. ` +
|
|
70
|
+
`Register it via IconRegistryService.registerIcon() before use.`);
|
|
71
|
+
}
|
|
72
|
+
return content;
|
|
73
|
+
}, ...(ngDevMode ? [{ debugName: "svgContent" }] : /* istanbul ignore next */ []));
|
|
74
|
+
effectiveAriaHidden = computed(() => {
|
|
75
|
+
const explicit = this.ariaHidden();
|
|
76
|
+
if (explicit !== null) {
|
|
77
|
+
return explicit;
|
|
78
|
+
}
|
|
79
|
+
return this.ariaLabel() === null;
|
|
80
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveAriaHidden" }] : /* istanbul ignore next */ []));
|
|
81
|
+
sizePresets = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
82
|
+
isCustomSize = computed(() => !this.sizePresets.includes(this.size()), ...(ngDevMode ? [{ debugName: "isCustomSize" }] : /* istanbul ignore next */ []));
|
|
83
|
+
classes = computed(() => {
|
|
84
|
+
return [
|
|
85
|
+
'fk-icon',
|
|
86
|
+
this.isCustomSize() ? '' : `fk-icon--${this.size()}`,
|
|
87
|
+
this.color() !== 'inherit' ? `fk-icon--${this.color()}` : '',
|
|
88
|
+
this.className(),
|
|
89
|
+
]
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.join(' ');
|
|
92
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
93
|
+
get hostClass() {
|
|
94
|
+
return this.classes();
|
|
95
|
+
}
|
|
96
|
+
get hostFontSize() {
|
|
97
|
+
return this.isCustomSize() ? this.size() : null;
|
|
98
|
+
}
|
|
99
|
+
get hostRole() {
|
|
100
|
+
return this.ariaLabel() ? 'img' : null;
|
|
101
|
+
}
|
|
102
|
+
get hostAriaLabel() {
|
|
103
|
+
return this.ariaLabel();
|
|
104
|
+
}
|
|
105
|
+
get hostAriaHidden() {
|
|
106
|
+
return this.effectiveAriaHidden() ? 'true' : null;
|
|
107
|
+
}
|
|
108
|
+
get hostId() {
|
|
109
|
+
return this.id();
|
|
110
|
+
}
|
|
111
|
+
get hostColor() {
|
|
112
|
+
const color = this.color();
|
|
113
|
+
const presets = [
|
|
114
|
+
'inherit',
|
|
115
|
+
'default',
|
|
116
|
+
'muted',
|
|
117
|
+
'primary',
|
|
118
|
+
'danger',
|
|
119
|
+
'success',
|
|
120
|
+
];
|
|
121
|
+
if (presets.includes(color)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return color;
|
|
125
|
+
}
|
|
126
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
127
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: IconComponent, isStandalone: true, selector: "fk-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaHidden: { classPropertyName: "ariaHidden", publicName: "ariaHidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass", "style.font-size": "this.hostFontSize", "attr.role": "this.hostRole", "attr.aria-label": "this.hostAriaLabel", "attr.aria-hidden": "this.hostAriaHidden", "attr.id": "this.hostId", "style.color": "this.hostColor" } }, ngImport: i0, template: "@if (svgContent()) {\n <span class=\"fk-icon__svg\" [innerHTML]=\"svgContent()\"></span>\n}\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1;vertical-align:middle}.fk-icon__svg{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep svg{width:1em;height:1em;display:block}:host.fk-icon--xs{font-size:var(--fk-icon-size-xs, .75rem)}:host.fk-icon--sm{font-size:var(--fk-icon-size-sm, 1rem)}:host.fk-icon--md{font-size:var(--fk-icon-size-md, 1.25rem)}:host.fk-icon--lg{font-size:var(--fk-icon-size-lg, 1.5rem)}:host.fk-icon--xl{font-size:var(--fk-icon-size-xl, 2rem)}:host.fk-icon--default{color:var(--fk-icon-color-default, var(--fk-color-text, #1f2d3d))}:host.fk-icon--muted{color:var(--fk-icon-color-muted, var(--fk-color-muted, #8a98a8))}:host.fk-icon--primary{color:var(--fk-icon-color-primary, var(--fk-color-primary, #0a84ff))}:host.fk-icon--danger{color:var(--fk-icon-color-danger, var(--fk-color-danger, #e02424))}:host.fk-icon--success{color:var(--fk-icon-color-success, var(--fk-color-success, #10b981))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
128
|
+
}
|
|
129
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IconComponent, decorators: [{
|
|
130
|
+
type: Component,
|
|
131
|
+
args: [{ selector: 'fk-icon', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (svgContent()) {\n <span class=\"fk-icon__svg\" [innerHTML]=\"svgContent()\"></span>\n}\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1;vertical-align:middle}.fk-icon__svg{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep svg{width:1em;height:1em;display:block}:host.fk-icon--xs{font-size:var(--fk-icon-size-xs, .75rem)}:host.fk-icon--sm{font-size:var(--fk-icon-size-sm, 1rem)}:host.fk-icon--md{font-size:var(--fk-icon-size-md, 1.25rem)}:host.fk-icon--lg{font-size:var(--fk-icon-size-lg, 1.5rem)}:host.fk-icon--xl{font-size:var(--fk-icon-size-xl, 2rem)}:host.fk-icon--default{color:var(--fk-icon-color-default, var(--fk-color-text, #1f2d3d))}:host.fk-icon--muted{color:var(--fk-icon-color-muted, var(--fk-color-muted, #8a98a8))}:host.fk-icon--primary{color:var(--fk-icon-color-primary, var(--fk-color-primary, #0a84ff))}:host.fk-icon--danger{color:var(--fk-icon-color-danger, var(--fk-color-danger, #e02424))}:host.fk-icon--success{color:var(--fk-icon-color-success, var(--fk-color-success, #10b981))}\n"] }]
|
|
132
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHidden", required: false }] }], hostClass: [{
|
|
133
|
+
type: HostBinding,
|
|
134
|
+
args: ['class']
|
|
135
|
+
}], hostFontSize: [{
|
|
136
|
+
type: HostBinding,
|
|
137
|
+
args: ['style.font-size']
|
|
138
|
+
}], hostRole: [{
|
|
139
|
+
type: HostBinding,
|
|
140
|
+
args: ['attr.role']
|
|
141
|
+
}], hostAriaLabel: [{
|
|
142
|
+
type: HostBinding,
|
|
143
|
+
args: ['attr.aria-label']
|
|
144
|
+
}], hostAriaHidden: [{
|
|
145
|
+
type: HostBinding,
|
|
146
|
+
args: ['attr.aria-hidden']
|
|
147
|
+
}], hostId: [{
|
|
148
|
+
type: HostBinding,
|
|
149
|
+
args: ['attr.id']
|
|
150
|
+
}], hostColor: [{
|
|
151
|
+
type: HostBinding,
|
|
152
|
+
args: ['style.color']
|
|
153
|
+
}] } });
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Registers icons into the `IconRegistryService` via Angular DI.
|
|
157
|
+
*
|
|
158
|
+
* Icons are provided as a record of name → SVG string pairs.
|
|
159
|
+
* This is tree-shakeable: only icons you import and pass here are bundled.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* import { searchIcon, lockIcon } from '@my-app/icons';
|
|
164
|
+
*
|
|
165
|
+
* export const appConfig: ApplicationConfig = {
|
|
166
|
+
* providers: [
|
|
167
|
+
* provideIcons({
|
|
168
|
+
* search: searchIcon,
|
|
169
|
+
* lock: lockIcon,
|
|
170
|
+
* }),
|
|
171
|
+
* ],
|
|
172
|
+
* };
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function provideIcons(icons) {
|
|
176
|
+
return makeEnvironmentProviders([
|
|
177
|
+
IconRegistryService,
|
|
178
|
+
provideAppInitializer(() => {
|
|
179
|
+
inject(IconRegistryService).registerIcons(icons);
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generated bundle index. Do not edit.
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
export { IconComponent, IconRegistryService, provideIcons };
|
|
189
|
+
//# sourceMappingURL=frame-kit-ui-ng-core-icon.mjs.map
|