@zenit-hosting/zenit-ui 0.1.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 +400 -0
- package/fesm2022/zenit-hosting-zenit-ui.mjs +11289 -0
- package/fesm2022/zenit-hosting-zenit-ui.mjs.map +1 -0
- package/llms-full.txt +5120 -0
- package/llms.txt +179 -0
- package/package.json +44 -0
- package/schematics/collection.json +15 -0
- package/schematics/migrate-material/component.js +241 -0
- package/schematics/migrate-material/index.js +463 -0
- package/schematics/migrate-material/report.js +125 -0
- package/schematics/migrate-material/schema.js +2 -0
- package/schematics/migrate-material/schema.json +32 -0
- package/schematics/migrate-material/tables.js +138 -0
- package/schematics/migrate-material/template.js +871 -0
- package/schematics/ng-add/html.js +151 -0
- package/schematics/ng-add/index.js +609 -0
- package/schematics/ng-add/init-script.js +108 -0
- package/schematics/ng-add/schema.js +2 -0
- package/schematics/ng-add/schema.json +34 -0
- package/schematics/package.json +3 -0
- package/styles/_daten.css +138 -0
- package/styles/_formulare.css +97 -0
- package/styles/_grundlage.css +424 -0
- package/styles/_konfigurator.css +216 -0
- package/styles/_navigation.css +179 -0
- package/styles/_overlays.css +86 -0
- package/styles/_rueckmeldung.css +100 -0
- package/styles/_werkzeuge.css +119 -0
- package/styles/themes/accents.css +91 -0
- package/styles/themes/base.css +26 -0
- package/styles/themes/contrast.css +67 -0
- package/styles/themes/light.css +74 -0
- package/styles/themes.css +21 -0
- package/styles/tokens.css +76 -0
- package/styles/zenit-ui.css +12 -0
- package/types/zenit-hosting-zenit-ui.d.ts +5957 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# zenit-ui
|
|
2
|
+
|
|
3
|
+
> Component library 0.1.0 for Angular 22 of the Zenit design system:
|
|
4
|
+
> 84 standalone components and directives, dark by default, token-driven,
|
|
5
|
+
> `@angular/cdk` for overlays and no Angular Material anywhere. This file is the index;
|
|
6
|
+
> `llms-full.txt` next to it is the complete reference and needs no other file.
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
Standalone components and directives with `OnPush` and signal inputs, for a game server hoster:
|
|
11
|
+
public website, customer area, server panels. They ship no styles of their own — every class
|
|
12
|
+
lives in the bundled CSS and uses only the tokens of `tokens.css`. Form fields are native
|
|
13
|
+
elements with library classes; overlays, focus traps and menus come from `@angular/cdk`.
|
|
14
|
+
Business logic, routes and copy stay in your application.
|
|
15
|
+
|
|
16
|
+
## Hard rules
|
|
17
|
+
|
|
18
|
+
- **No `@angular/material`**, not even temporarily. The one exception is the Material Icons
|
|
19
|
+
webfont. Peers: `@angular/core`, `common`, `forms`, `cdk`, `rxjs`; nothing else.
|
|
20
|
+
- **Tokens only.** Colour, spacing, radius, font and shadow come from `var(--…)`. Write no hex,
|
|
21
|
+
no `rgb()`, no pixel value of your own; `tokens.css` is the only place that has them. The two
|
|
22
|
+
exceptions are not design values: the grid tracks of `columns` on `z-rows` and the `width` of
|
|
23
|
+
a `z-skeleton`.
|
|
24
|
+
- **Numbers**: comma as the decimal mark and a non-breaking space before unit and currency.
|
|
25
|
+
` ` in a template, `\u00a0` in a TypeScript string, or `Intl.NumberFormat('de-DE', …)`,
|
|
26
|
+
which emits it. The library formats nothing.
|
|
27
|
+
- **`class="z-root"` on `<html>` and on `<body>`**, or controls fall back to Arial, the focus
|
|
28
|
+
ring is gone and overlays on `body` lose the variables.
|
|
29
|
+
- **Style order is binding**, by package specifier: `zenit-ui/styles/tokens.css`,
|
|
30
|
+
`@angular/cdk/overlay-prebuilt.css`, [`zenit-ui/styles/themes.css`],
|
|
31
|
+
`zenit-ui/styles/zenit-ui.css`, your own. Later rules of equal weight win.
|
|
32
|
+
- **`<z-toast-outlet />` exactly once** in the shell, or `ZToast` shows nothing.
|
|
33
|
+
- **`@angular/cdk/overlay-prebuilt.css` is required**: dialog, menu, tooltip and the combobox
|
|
34
|
+
panel are CDK overlays and are unpositioned without it.
|
|
35
|
+
- **Self-host Material Icons**: `@import "material-icons/iconfont/filled.css" layer(schriften);`
|
|
36
|
+
— without the layer `.z-icon` loses against the font's own `font-size`.
|
|
37
|
+
- **Providers at the root**: `provideZenitLabels(…)` for the built-in texts,
|
|
38
|
+
`provideZenitTheme(…)` for schemes and accents.
|
|
39
|
+
- **Banned**: gradients, `backdrop-filter`, `text-shadow`, `filter`, coloured `box-shadow`,
|
|
40
|
+
grid backgrounds, all-caps labels, icon backplates, `::ng-deep`, `!important`.
|
|
41
|
+
- **Copy comes from you.** The library holds no product strings, only accessible names.
|
|
42
|
+
|
|
43
|
+
## Setup in 6 steps
|
|
44
|
+
|
|
45
|
+
1. `npm i ./zenit-ui-0.1.0.tgz` (the package is not on npm). `ng add ./zenit-ui-0.1.0.tgz --themes`
|
|
46
|
+
does steps 2 to 6 for you.
|
|
47
|
+
2. Register the stylesheets in `angular.json`, in this order:
|
|
48
|
+
`zenit-ui/styles/tokens.css`, `@angular/cdk/overlay-prebuilt.css`,
|
|
49
|
+
`zenit-ui/styles/themes.css` (opt-in: light, contrast, accents),
|
|
50
|
+
`zenit-ui/styles/zenit-ui.css`, then your own.
|
|
51
|
+
3. Put `class="z-root"` on `<html>` and on `<body>`.
|
|
52
|
+
4. Self-host the fonts: `material-icons/iconfont/filled.css` with `layer(schriften)`, plus
|
|
53
|
+
Inter 400/500/600, Space Grotesk 600/700, JetBrains Mono 400/600.
|
|
54
|
+
5. Put `<z-toast-outlet />` once into the application shell.
|
|
55
|
+
6. `provideZenitTheme({ defaultScheme: 'system' })` and, for English, `provideZenitLabels(Z_LABELS_EN)`
|
|
56
|
+
at the application root; `zenitThemeInitScript()` inline in `<head>` against the flash.
|
|
57
|
+
|
|
58
|
+
Components are standalone: import the class into `imports`. No `NgModule`, no `forRoot`.
|
|
59
|
+
|
|
60
|
+
## Components and directives
|
|
61
|
+
|
|
62
|
+
Selector, import name, purpose.
|
|
63
|
+
|
|
64
|
+
- `z-icon` ZIcon — Material Icons ligature.
|
|
65
|
+
- `z-spinner` ZSpinner — Loading indicator.
|
|
66
|
+
- `button[zBtn], a[zBtn]` ZButton — Triggers an action, on a native `<button>` or on an `<a>`.
|
|
67
|
+
- `z-badge` ZBadge — Status or tag in one word.
|
|
68
|
+
- `z-field` ZField — Wrapper around a form control: label above, hint or…
|
|
69
|
+
- `input[zInput], textarea[zInput]` ZInput — Native `<input>` or `<textarea>` in Zenit style.
|
|
70
|
+
- `z-input-group` ZInputGroup — Field with a leading icon, for example a search box.
|
|
71
|
+
- `z-select` ZSelect — Wrapper around a native `<select>`.
|
|
72
|
+
- `[zPanelActions]` ZPanelActions — Marks the element that is projected into the panel…
|
|
73
|
+
- `z-panel` ZPanel — The only container with a border.
|
|
74
|
+
- `a[zSkipLink]` ZSkipLink — The first tab stop of a page: a link that jumps past…
|
|
75
|
+
- `z-checkbox` ZCheckbox — Selects entries for a bulk action or confirms a…
|
|
76
|
+
- `z-toggle` ZToggle — Switches a setting that takes effect without a save…
|
|
77
|
+
- `z-setting` ZSetting — One row of a settings list: title, configuration key and…
|
|
78
|
+
- `z-slider` ZSlider — Sets an amount on a fixed scale, on a native…
|
|
79
|
+
- `z-segment` ZSegment — Segment switches the view on the same data, at most four…
|
|
80
|
+
- `nav[zTabs]` ZTabs — Tab bar for the sub-pages of one area.
|
|
81
|
+
- `a[zTab]` ZTab — One tab inside `nav[zTabs]`, always a link.
|
|
82
|
+
- `z-tab-panel` ZTabPanel — One view of a `z-tab-group`: its caption in the tab bar…
|
|
83
|
+
- `z-tab-group` ZTabGroup — Tabs for views that have no address of their own: the…
|
|
84
|
+
- `z-stepper` ZStepper — Step indicator for a multi-step flow.
|
|
85
|
+
- `[zSidebarItem]` ZSidebarItem — One entry of the sidebar, a Material Icon plus a noun.
|
|
86
|
+
- `z-sidebar-group` ZSidebarGroup — Group of sidebar entries with one word as its heading.
|
|
87
|
+
- `z-sidebar` ZSidebar — Navigation inside a server panel: at most four groups…
|
|
88
|
+
- `[zBrand]` ZBrand — Brand slot of the header: plain text in the `display`…
|
|
89
|
+
- `a[zHeaderLink]` ZHeaderLink — One link of the main navigation, carrying the class…
|
|
90
|
+
- `[zHeaderEnd]` ZHeaderEnd — Marks content for the right-hand end of the header…
|
|
91
|
+
- `z-app-header` ZAppHeader — Header of the customer area and of the public pages.
|
|
92
|
+
- `z-page-header` ZPageHeader — Head of a page in the customer area: title on the left…
|
|
93
|
+
- `[zFooterBase]` ZFooterBase — Marks the bottom row of the footer: copyright on the…
|
|
94
|
+
- `z-footer-col` ZFooterCol — One link column of the public footer.
|
|
95
|
+
- `z-footer` ZFooter — Footer of every page.
|
|
96
|
+
- `z-metrics` ZMetrics — Row of metrics inside ONE panel, laid out as columns…
|
|
97
|
+
- `z-metric` ZMetric — A single figure with its unit, optionally with a usage…
|
|
98
|
+
- `z-rows` ZRows — Row pattern for everything the customer owns: servers…
|
|
99
|
+
- `z-rows-head` ZRowsHead — Column head of the list, carrying the class…
|
|
100
|
+
- `a[zRow], div[zRow]` ZRow — One row, carrying the class `z-row`.
|
|
101
|
+
- `[zRowTitle]` ZRowTitle — Pure slot marker for the title of a row, so `z-row-main`…
|
|
102
|
+
- `[zRowMeta]` ZRowMeta — Slot for the meta line of a row when that line is more…
|
|
103
|
+
- `a[zRowLink]` ZRowLink — Link inside a `div[zRow]` that makes the whole row…
|
|
104
|
+
- `[zRowAction]` ZRowAction — Action inside a row that holds an `ZRowLink`: adds the…
|
|
105
|
+
- `[zRowThumb]` ZRowThumb — Pure slot marker for an own medium in the thumbnail of a…
|
|
106
|
+
- `z-row-main` ZRowMain — First column of a row: thumbnail, title and one meta line.
|
|
107
|
+
- `[zRowNum]` ZRowNum — Amount or number inside a row: right-aligned in the mono…
|
|
108
|
+
- `table[zTable]` ZTable — Table for files, invoices, backups and databases…
|
|
109
|
+
- `z-table-container` ZTableContainer — Wrapper around `table[zTable]`.
|
|
110
|
+
- `[zNum]` ZNum — Right-aligned cell in the mono face: size, date, amount.
|
|
111
|
+
- `[zTableName]` ZTableName — Name cell: icon and name in the mono face.
|
|
112
|
+
- `th[zSortHeader]` ZSortHeader — Header cell that sorts its column.
|
|
113
|
+
- `z-pagination` ZPagination — Pages through lists with more than `pageSize` entries…
|
|
114
|
+
- `[zAlertAction]` ZAlertAction — Marks the single action of an alert, projected next to…
|
|
115
|
+
- `z-alert` ZAlert — Note in the page flow: title as a full sentence, the…
|
|
116
|
+
- `[zEmptyAction]` ZEmptyAction — Marks the single action of an empty state: one secondary…
|
|
117
|
+
- `z-empty-state` ZEmptyState — Fills a list that has no entries: a title naming the…
|
|
118
|
+
- `z-skeleton` ZSkeleton — Placeholder that holds the space while a list or metric…
|
|
119
|
+
- `z-toast-outlet` ZToastOutlet — Stands once in the root template and renders the toasts…
|
|
120
|
+
- `[zTooltip]` ZTooltip — Short addition to a control.
|
|
121
|
+
- `[zDialogActions]` ZDialogActions — Marks the buttons in the footer of a dialog.
|
|
122
|
+
- `z-dialog` ZDialogLayout — Layout of a dialog: header with the heading, body…
|
|
123
|
+
- `z-menu` ZMenu — Surface of the menu, which collects the rarer actions of…
|
|
124
|
+
- `button[zMenuItem], a[zMenuItem]` ZMenuItem — One entry of the menu, an icon plus a verb and its…
|
|
125
|
+
- `z-menu-separator` ZMenuSeparator — Separator in front of the destructive entries.
|
|
126
|
+
- `z-console` ZConsole — Live log of a server with a command line underneath.
|
|
127
|
+
- `[zHeroActions]` ZHeroActions — Marks the at most two buttons of the hero, one `primary`…
|
|
128
|
+
- `[zHeroAside]` ZHeroAside — Marks the right-hand column of the hero: a price list…
|
|
129
|
+
- `z-hero` ZHero — Head of a public page: one statement, one sentence, at…
|
|
130
|
+
- `z-game-grid` ZGameGrid — Grid of the game tiles: `auto-fill` from 128px width, so…
|
|
131
|
+
- `button[zGameTile]` ZGameTile — Selectable game tile in the price calculator on the…
|
|
132
|
+
- `a[zGameTile]` ZGameTileLink — Game tile as a link, for a grid whose tiles lead…
|
|
133
|
+
- `z-price-summary` ZPriceSummary — Result of the price calculator, carrying the only…
|
|
134
|
+
- `z-spec-list` ZSpecList — Facts as term and value pairs.
|
|
135
|
+
- `z-faq` ZFaq — One question of the FAQ.
|
|
136
|
+
- `z-option-group` ZOptionGroup — Picks exactly one of a few options that are meant to be…
|
|
137
|
+
- `z-combobox` ZCombobox — Picks one value out of a long list by typing and…
|
|
138
|
+
- `[zWizardActions]` ZWizardActions — Marks an action of the current step, which goes into the…
|
|
139
|
+
- `z-wizard-step` ZWizardStep — One step of a `z-wizard`: head with number and title…
|
|
140
|
+
- `z-wizard` ZWizard — The steps of an order, one below the other, with the…
|
|
141
|
+
- `[zConfigAside]` ZConfigAside — Marks the column next to the form: the `z-price-summary`…
|
|
142
|
+
- `z-config` ZConfig — Layout of a configurator: the form on the left, the…
|
|
143
|
+
- `z-sticky-bar` ZStickyBar — Keeps price and next step in view at the bottom edge of…
|
|
144
|
+
- `z-included-list` ZIncludedList — Lists what every plan includes without a surcharge.
|
|
145
|
+
- `z-disclosure` ZDisclosure — Folds away settings most visitors do not need: build…
|
|
146
|
+
- `z-input-action` ZInputAction — A field with a button that checks or applies the value…
|
|
147
|
+
- `z-cost-chart` ZCostChart — Shows how flex costs grow with the hours played and…
|
|
148
|
+
|
|
149
|
+
## Services, providers and tokens
|
|
150
|
+
|
|
151
|
+
- `provideZenitToast` (provider) — Sets how many toasts `ZToast` shows at once and what…
|
|
152
|
+
- `ZToast` (service) — Short feedback above the content: confirms that…
|
|
153
|
+
- `ZDialog` (service) — Thin wrapper around `Dialog` from `@angular/cdk/dialog`…
|
|
154
|
+
- `zCostAt` (function) — What a month costs after a number of hours: the base…
|
|
155
|
+
- `zCostCapHour` (function) — The hour from which the cap holds…
|
|
156
|
+
- `Z_LABELS` (token) — The label registry the components read.
|
|
157
|
+
- `provideZenitLabels` (provider) — Overrides label defaults, for the whole application or…
|
|
158
|
+
- `injectZLabels` (function) — Reads the label registry of the current injector…
|
|
159
|
+
- `provideZenitTheme` (provider) — Registers the theme of the library and applies the…
|
|
160
|
+
- `ZTheme` (service) — Reads and switches the colour scheme and the accent.
|
|
161
|
+
- `zenitThemeInitScript` (function) — Returns the source of a small script that applies the…
|
|
162
|
+
|
|
163
|
+
## Exported types
|
|
164
|
+
|
|
165
|
+
`ZButtonVariant`, `ZBadgeStatus`, `ZSegmentOption`, `ZSort`, `ZAlertStatus`, `ZToastStatus`, `ZToastLive`, `ZToastOptions`, `ZToastItem`, `ZToastConfig`, `ZDialogConfig<D, R>`, `ZRestoreFocusTarget`, `ZConfirmConfig`, `ZConsoleLevel`, `ZConsoleLine`, `ZPriceLine`, `ZPriceTotal`, `ZSpecItem`, `ZOption<T extends string | number = string>`, `ZComboOption`, `ZWizardState`, `ZLabels`, `ZThemeConfig`, `ZThemeInitConfig`.
|
|
166
|
+
|
|
167
|
+
## Exported constants
|
|
168
|
+
|
|
169
|
+
Values to import and use, not shapes to implement.
|
|
170
|
+
|
|
171
|
+
- `Z_MENU` — All menu building blocks at once, for the `imports` of a…
|
|
172
|
+
- `Z_LABELS_DE` — German defaults, the value `Z_LABELS` falls back to.
|
|
173
|
+
- `Z_LABELS_EN` — English equivalents of `Z_LABELS_DE`, complete and ready…
|
|
174
|
+
|
|
175
|
+
## Full reference
|
|
176
|
+
|
|
177
|
+
- `llms-full.txt` — every input with type and default, every output, slot, example and
|
|
178
|
+
"do not", plus forms, theming, labels, services, the mat-* migration table and all tokens.
|
|
179
|
+
- `styles/tokens.css` — the token values themselves.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zenit-hosting/zenit-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@angular/cdk": "^22.0.0",
|
|
10
|
+
"@angular/common": "^22.0.0",
|
|
11
|
+
"@angular/core": "^22.0.0",
|
|
12
|
+
"@angular/forms": "^22.0.0",
|
|
13
|
+
"rxjs": "^7.8.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"tslib": "^2.3.0"
|
|
17
|
+
},
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"schematics": "./schematics/collection.json",
|
|
20
|
+
"ng-add": {
|
|
21
|
+
"save": "dependencies"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
"./styles/*": {
|
|
25
|
+
"default": "./styles/*"
|
|
26
|
+
},
|
|
27
|
+
"./llms.txt": {
|
|
28
|
+
"default": "./llms.txt"
|
|
29
|
+
},
|
|
30
|
+
"./llms-full.txt": {
|
|
31
|
+
"default": "./llms-full.txt"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": {
|
|
34
|
+
"default": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./types/zenit-hosting-zenit-ui.d.ts",
|
|
38
|
+
"default": "./fesm2022/zenit-hosting-zenit-ui.mjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"module": "fesm2022/zenit-hosting-zenit-ui.mjs",
|
|
42
|
+
"typings": "types/zenit-hosting-zenit-ui.d.ts",
|
|
43
|
+
"type": "module"
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
|
|
3
|
+
"schematics": {
|
|
4
|
+
"ng-add": {
|
|
5
|
+
"description": "Wire zenit-ui into an application: styles, z-root, fonts and the toast outlet.",
|
|
6
|
+
"factory": "./ng-add/index#ngAdd",
|
|
7
|
+
"schema": "./ng-add/schema.json"
|
|
8
|
+
},
|
|
9
|
+
"migrate-material": {
|
|
10
|
+
"description": "Rewrite the mechanical Angular Material usages (mat-icon, mat-*-button, matTooltip, mat-spinner, mat-chip, Mat* imports) to zenit-ui and report everything that needs a human.",
|
|
11
|
+
"factory": "./migrate-material/index#migrateMaterial",
|
|
12
|
+
"schema": "./migrate-material/schema.json"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findComponents = findComponents;
|
|
7
|
+
exports.planImports = planImports;
|
|
8
|
+
exports.listEdits = listEdits;
|
|
9
|
+
/**
|
|
10
|
+
* The TypeScript half of `migrate-material`: finds the `@Component` classes of a
|
|
11
|
+
* file with their template, and plans the edits to `imports: [...]` and to the
|
|
12
|
+
* ES imports. No type information is used, a symbol is recognised by its name.
|
|
13
|
+
*/
|
|
14
|
+
const ast_utils_1 = require("@schematics/angular/utility/ast-utils");
|
|
15
|
+
const eol_1 = require("@schematics/angular/utility/eol");
|
|
16
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
17
|
+
const tables_1 = require("./tables");
|
|
18
|
+
function findComponents(source) {
|
|
19
|
+
return (0, ast_utils_1.getDecoratorMetadata)(source, 'Component', '@angular/core')
|
|
20
|
+
.filter(typescript_1.default.isObjectLiteralExpression)
|
|
21
|
+
.map((metadata) => {
|
|
22
|
+
// object literal -> call -> decorator -> class
|
|
23
|
+
const owner = metadata.parent?.parent?.parent;
|
|
24
|
+
const info = {
|
|
25
|
+
name: owner && typescript_1.default.isClassDeclaration(owner) && owner.name ? owner.name.text : '(anonymous)',
|
|
26
|
+
offset: metadata.getStart(),
|
|
27
|
+
metadata,
|
|
28
|
+
};
|
|
29
|
+
const templateUrl = property(metadata, 'templateUrl');
|
|
30
|
+
const template = property(metadata, 'template');
|
|
31
|
+
if (templateUrl && typescript_1.default.isStringLiteralLike(templateUrl)) {
|
|
32
|
+
info.templateUrl = templateUrl.text;
|
|
33
|
+
}
|
|
34
|
+
else if (template) {
|
|
35
|
+
const raw = template.getText();
|
|
36
|
+
if (!typescript_1.default.isStringLiteralLike(template)) {
|
|
37
|
+
info.inlineProblem =
|
|
38
|
+
'The inline template is not a plain string or a template literal without ${...}.';
|
|
39
|
+
}
|
|
40
|
+
else if (raw.includes('\\')) {
|
|
41
|
+
// An escape makes the text differ from what Angular parses, so spans would not line up.
|
|
42
|
+
info.inlineProblem = 'The inline template contains escape sequences (backslash).';
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
info.inline = {
|
|
46
|
+
start: template.getStart() + 1,
|
|
47
|
+
end: template.getEnd() - 1,
|
|
48
|
+
delimiter: raw[0],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return info;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function property(literal, name) {
|
|
56
|
+
const found = literal.properties.find((node) => typescript_1.default.isPropertyAssignment(node) &&
|
|
57
|
+
(typescript_1.default.isIdentifier(node.name) || typescript_1.default.isStringLiteral(node.name)) &&
|
|
58
|
+
node.name.text === name);
|
|
59
|
+
return found?.initializer;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Plans the edits of one file for all its components: Material symbols whose
|
|
63
|
+
* selectors are gone leave `imports`, the zenit-ui symbols the template needs
|
|
64
|
+
* are added, and the ES imports follow.
|
|
65
|
+
*/
|
|
66
|
+
function planImports(source, components) {
|
|
67
|
+
const text = source.text;
|
|
68
|
+
const eol = (0, eol_1.getEOL)(text);
|
|
69
|
+
const edits = [];
|
|
70
|
+
const findings = [];
|
|
71
|
+
const removed = new Set();
|
|
72
|
+
const added = new Set();
|
|
73
|
+
let changed = 0;
|
|
74
|
+
for (const [component, usage] of components) {
|
|
75
|
+
const unused = Object.keys(tables_1.MATERIAL_SYMBOLS).filter((symbol) => !Object.keys(usage.remaining).some((name) => tables_1.MATERIAL_SYMBOLS[symbol].test(name)));
|
|
76
|
+
const manual = (code, reason) => {
|
|
77
|
+
if (usage.zenit.length === 0) {
|
|
78
|
+
return; // nothing of zenit-ui in the template, so nothing to tell
|
|
79
|
+
}
|
|
80
|
+
const wanted = usage.zenit.length ? `add ${usage.zenit.join(', ')} from 'zenit-ui'` : '';
|
|
81
|
+
findings.push({
|
|
82
|
+
offset: component.offset,
|
|
83
|
+
rule: 'imports',
|
|
84
|
+
kind: 'manual',
|
|
85
|
+
code,
|
|
86
|
+
reason: `${component.name}: ${reason} The file was not changed.`,
|
|
87
|
+
fix: [wanted, 'remove the Material modules whose selectors are gone from the template']
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join('; ') + '.',
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const standalone = property(component.metadata, 'standalone');
|
|
93
|
+
if (standalone?.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
|
|
94
|
+
manual('imports-ngmodule', 'the component is declared in an NgModule (standalone: false), so its imports live in that module.');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const imports = property(component.metadata, 'imports');
|
|
98
|
+
const list = imports && typescript_1.default.isArrayLiteralExpression(imports) ? imports.elements : undefined;
|
|
99
|
+
if (imports && (!list || list.some((element) => !typescript_1.default.isIdentifier(element)))) {
|
|
100
|
+
manual('imports-not-literal', `"imports" is not a plain array of identifiers (${imports.getText().replace(/\s+/g, ' ').slice(0, 60)}), a shared array is not followed.`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const names = new Set((list ?? []).map((element) => element.getText()));
|
|
104
|
+
const drop = (list ?? []).filter((element) => unused.includes(element.getText()));
|
|
105
|
+
const add = usage.zenit.filter((symbol) => !names.has(symbol));
|
|
106
|
+
if (drop.length === 0 && add.length === 0) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
changed += 1;
|
|
110
|
+
drop.forEach((element) => removed.add(element));
|
|
111
|
+
add.forEach((symbol) => added.add(symbol));
|
|
112
|
+
if (!list) {
|
|
113
|
+
// No `imports` yet: a new first property, like `ng add` writes it.
|
|
114
|
+
const at = component.metadata.getStart() + 1;
|
|
115
|
+
edits.push({ start: at, end: at, text: `${eol} imports: [${add.join(', ')}],` });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
edits.push(...listEdits(text, list, drop, add, eol));
|
|
119
|
+
}
|
|
120
|
+
if (removed.size === 0 && added.size === 0) {
|
|
121
|
+
return { edits, findings, changed };
|
|
122
|
+
}
|
|
123
|
+
// ES imports: a Material symbol goes when nothing in the file names it any more.
|
|
124
|
+
const declarations = source.statements.filter(typescript_1.default.isImportDeclaration);
|
|
125
|
+
// Every remaining mention keeps the ES import; one outside an "imports" array is worth a look.
|
|
126
|
+
const references = new Map();
|
|
127
|
+
const outside = new Map();
|
|
128
|
+
const count = (node) => {
|
|
129
|
+
if (typescript_1.default.isIdentifier(node) && !removed.has(node) && !typescript_1.default.isImportSpecifier(node.parent)) {
|
|
130
|
+
references.set(node.text, (references.get(node.text) ?? 0) + 1);
|
|
131
|
+
const array = node.parent;
|
|
132
|
+
const owner = typescript_1.default.isArrayLiteralExpression(array) ? array.parent : undefined;
|
|
133
|
+
if (!(owner && typescript_1.default.isPropertyAssignment(owner) && owner.name.getText() === 'imports')) {
|
|
134
|
+
outside.set(node.text, outside.get(node.text) ?? node.getStart());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
typescript_1.default.forEachChild(node, count);
|
|
138
|
+
};
|
|
139
|
+
count(source);
|
|
140
|
+
const gone = new Set([...removed].map((node) => node.getText()));
|
|
141
|
+
const deleted = new Set();
|
|
142
|
+
for (const declaration of declarations) {
|
|
143
|
+
const bindings = declaration.importClause?.namedBindings;
|
|
144
|
+
if (!bindings || !typescript_1.default.isNamedImports(bindings)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const drop = bindings.elements.filter((specifier) => gone.has(specifier.name.text) && !references.get(specifier.name.text));
|
|
148
|
+
if (drop.length === bindings.elements.length && !declaration.importClause?.name) {
|
|
149
|
+
deleted.add(declaration);
|
|
150
|
+
const end = declaration.getEnd();
|
|
151
|
+
const lineEnd = text.startsWith(eol, end) ? end + eol.length : end;
|
|
152
|
+
edits.push({ start: declaration.getStart(), end: lineEnd, text: '' });
|
|
153
|
+
}
|
|
154
|
+
else if (drop.length > 0) {
|
|
155
|
+
edits.push(...listEdits(text, bindings.elements, drop, [], eol));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const name of [...gone].sort()) {
|
|
159
|
+
const at = outside.get(name);
|
|
160
|
+
if (at !== undefined) {
|
|
161
|
+
findings.push({
|
|
162
|
+
offset: at,
|
|
163
|
+
rule: 'imports',
|
|
164
|
+
kind: 'review',
|
|
165
|
+
code: 'imports-still-referenced',
|
|
166
|
+
reason: `${name} left "imports" but is still named elsewhere in the file (a query, an injection or a type).`,
|
|
167
|
+
fix: `Check what reads ${name}: its selector is gone from the template, so a query finds nothing.`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const wanted = [...added].sort();
|
|
172
|
+
if (wanted.length > 0) {
|
|
173
|
+
const zenit = declarations.find((declaration) => typescript_1.default.isStringLiteral(declaration.moduleSpecifier) &&
|
|
174
|
+
declaration.moduleSpecifier.text === 'zenit-ui');
|
|
175
|
+
const bindings = zenit?.importClause?.namedBindings;
|
|
176
|
+
if (zenit && !(bindings && typescript_1.default.isNamedImports(bindings) && !zenit.importClause?.isTypeOnly)) {
|
|
177
|
+
findings.push({
|
|
178
|
+
offset: zenit.getStart(),
|
|
179
|
+
rule: 'imports',
|
|
180
|
+
kind: 'manual',
|
|
181
|
+
code: 'imports-namespace',
|
|
182
|
+
reason: '"zenit-ui" is not imported through a plain named import, so the new symbols were not added to it.',
|
|
183
|
+
fix: `Import ${wanted.join(', ')} from 'zenit-ui' by hand.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else if (bindings && typescript_1.default.isNamedImports(bindings)) {
|
|
187
|
+
const present = new Set(bindings.elements.map((specifier) => specifier.name.text));
|
|
188
|
+
edits.push(...listEdits(text, bindings.elements, [], wanted.filter((symbol) => !present.has(symbol)), eol));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Behind the last import that stays: an offset inside a deleted line would collide with it.
|
|
192
|
+
const last = declarations.filter((declaration) => !deleted.has(declaration)).at(-1);
|
|
193
|
+
const quote = last?.moduleSpecifier.getText()[0] === '"' ? '"' : "'";
|
|
194
|
+
const line = `import { ${wanted.join(', ')} } from ${quote}zenit-ui${quote};`;
|
|
195
|
+
const at = last ? last.getEnd() : 0;
|
|
196
|
+
edits.push({ start: at, end: at, text: last ? `${eol}${line}` : `${line}${eol}` });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { edits, findings, changed };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Removes and appends entries of a comma-separated list (array elements, import
|
|
203
|
+
* specifiers) and keeps its layout: one entry per line stays one entry per line,
|
|
204
|
+
* a trailing comma stays.
|
|
205
|
+
*/
|
|
206
|
+
function listEdits(text, list, drop, add, eol) {
|
|
207
|
+
const edits = [];
|
|
208
|
+
const kept = list.filter((node) => !drop.includes(node));
|
|
209
|
+
const lastKept = kept.at(-1);
|
|
210
|
+
const last = list.at(-1);
|
|
211
|
+
if (last && !lastKept && add.length === 0) {
|
|
212
|
+
// Nothing stays: empty the brackets instead of leaving a blank line or a lone comma.
|
|
213
|
+
return [{ start: list.pos, end: last.parent.getEnd() - 1, text: '' }];
|
|
214
|
+
}
|
|
215
|
+
list.forEach((node, index) => {
|
|
216
|
+
// Entries behind the last kept one form the trailing run, removed in one piece below.
|
|
217
|
+
if (drop.includes(node) && lastKept && node.getStart() < lastKept.getStart()) {
|
|
218
|
+
// Up to the next entry, so the separator and the line break go with it.
|
|
219
|
+
edits.push({ start: node.getStart(), end: list[index + 1].getStart(), text: '' });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
if (last && last !== lastKept) {
|
|
223
|
+
const start = lastKept ? lastKept.getEnd() : list[0].getStart();
|
|
224
|
+
edits.push({ start, end: last.getEnd(), text: '' });
|
|
225
|
+
}
|
|
226
|
+
if (add.length > 0) {
|
|
227
|
+
const first = list.at(0);
|
|
228
|
+
const multiline = first !== undefined && /[\r\n]/.test(text.slice(list.pos, first.getStart()));
|
|
229
|
+
const indent = first
|
|
230
|
+
? text.slice(text.lastIndexOf('\n', first.getStart()) + 1, first.getStart())
|
|
231
|
+
: '';
|
|
232
|
+
const separator = multiline && /^\s*$/.test(indent) ? `,${eol}${indent}` : ', ';
|
|
233
|
+
const at = lastKept ? lastKept.getEnd() : (first?.getStart() ?? list.pos);
|
|
234
|
+
edits.push({
|
|
235
|
+
start: at,
|
|
236
|
+
end: at,
|
|
237
|
+
text: (lastKept ? separator : '') + add.join(separator),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return edits;
|
|
241
|
+
}
|