spiderly 19.8.4 → 19.8.5
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/agent/docs/angular-customization/SKILL.md +389 -0
- package/agent/docs/angular-customization/references/controls.generated.md +23 -0
- package/agent/docs/angular-customization/references/helper-functions.generated.md +39 -0
- package/agent/docs/angular-customization/references/ui-control-types.generated.md +24 -0
- package/agent/docs/angular-customization/references/validators.generated.md +13 -0
- package/agent/docs/authorization/SKILL.md +385 -0
- package/agent/docs/authorization/references/api-error-codes.generated.md +17 -0
- package/agent/docs/authorization/references/security-endpoints.generated.md +24 -0
- package/agent/docs/backend-hooks/SKILL.md +231 -0
- package/agent/docs/backend-localization/SKILL.md +170 -0
- package/agent/docs/backend-testing/SKILL.md +65 -0
- package/agent/docs/custom-endpoints/SKILL.md +409 -0
- package/agent/docs/e2e-testing/SKILL.md +139 -0
- package/agent/docs/entity-design/SKILL.md +346 -0
- package/agent/docs/entity-design/references/attributes.generated.md +53 -0
- package/agent/docs/file-storage/SKILL.md +262 -0
- package/agent/docs/filtering-patterns/SKILL.md +127 -0
- package/agent/docs/filtering-patterns/references/match-mode-codes.generated.md +15 -0
- package/agent/docs/frontend-localization/SKILL.md +120 -0
- package/agent/docs/mapper-customization/SKILL.md +105 -0
- package/agent/manifest.json +34 -0
- package/agent/skills/add-entity/SKILL.md +158 -0
- package/agent/skills/deployment/SKILL.md +551 -0
- package/agent/skills/ef-migrations/SKILL.md +49 -0
- package/agent/skills/report-gap/SKILL.md +110 -0
- package/agent/skills/report-gap/scripts/build-issue-url.mjs +82 -0
- package/agent/skills/spiderly-upgrade/SKILL.md +165 -0
- package/agent/skills/verify-ui/SKILL.md +148 -0
- package/agent/skills/verify-ui/scripts/get-admin-token.mjs +134 -0
- package/fesm2022/spiderly.mjs +11 -6
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +29 -3
- package/package.json +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-customization
|
|
3
|
+
description: Build, compose, or style any Spiderly Angular admin-panel UI — pages, cards, panels, dashboards, buttons, tables, dialogs, empty/loading states. Use to reuse existing Spiderly/PrimeNG components instead of hand-writing Tailwind/HTML, and when extending generated components, overriding form save behavior, configuring data tables, customizing layout/theme, or adding validators. For translating UI strings (Transloco, assets/i18n), use the frontend-localization skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Angular Customization
|
|
7
|
+
|
|
8
|
+
> **Scope:** Angular admin panel only — not storefront apps (Next.js + shadcn/Tailwind), where plain Tailwind is correct.
|
|
9
|
+
|
|
10
|
+
## Reuse components before hand-writing UI
|
|
11
|
+
|
|
12
|
+
Before building or restyling any admin UI, check for an existing component first. Prefer: **Spiderly (`spiderly-*`) → PrimeNG (`p-*`) → raw Tailwind/HTML.** Spiderly is built on PrimeNG, so the theme applies to both. See the catalogs below; unsure a wrapper exists? `grep -rE "selector:.*spiderly-" projects/spiderly/src/lib`.
|
|
13
|
+
|
|
14
|
+
**Look before you build — don't force-fit a component you'd have to hack.** Raw Tailwind/HTML is correct for pure layout/spacing, one-off widgets with no component analog, or when forcing a component means fighting it. When you hand-roll, state in one line why no component fit.
|
|
15
|
+
|
|
16
|
+
**Same rule for utility functions.** Before writing a formatting / date / file / dropdown-option helper, check the shared catalog — see [references/helper-functions.generated.md](references/helper-functions.generated.md) (from `helper-functions.ts`). Re-implementing `exportListToExcel`, `getPrimengDropdownNamebookOptions`, `kebabToTitleCase`, and friends is a common, avoidable duplication.
|
|
17
|
+
|
|
18
|
+
Keep admin UI **responsive and mobile-first**, matching the layout/grid conventions of surrounding pages rather than inventing a new one.
|
|
19
|
+
|
|
20
|
+
## Global loading & error handling — don't hand-roll either
|
|
21
|
+
|
|
22
|
+
Two cross-cutting behaviors ship with every Spiderly admin and cover **every** generated-client call automatically. Re-implementing them per call is the most common needless boilerplate in admin code:
|
|
23
|
+
|
|
24
|
+
- **Loading:** an HTTP interceptor shows the global full-screen blocking spinner for every request and hides it when the request settles. Read-shaped responses (autocomplete, dropdowns, table pagination, bare-scalar GETs) are exempted server-side via `[SkipSpinner]` inference — see the `custom-endpoints` skill to tune that per endpoint. Don't add per-component spinners around API calls; for layout placeholders use `card-skeleton` (catalog below).
|
|
25
|
+
- **Errors:** an HTTP interceptor toasts every failed request — server-unreachable warning, the server's `BusinessException` message on 400, login/permission/not-found messages on 401/403/404, a generic error toast for the rest — then **rethrows**, so callers only ever run their success path. Uncaught non-HTTP runtime errors get a generic toast from the global `ErrorHandler`. So: **subscribe to the success path only** — no per-call `catchError` toasts, no `try/catch` around generated client calls. Add `catchError` only when you need to *react* to a specific failure (e.g. roll back optimistic UI state); the user-facing message has already been shown by the time your handler runs.
|
|
26
|
+
|
|
27
|
+
## Generated File Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Frontend/src/app/business/
|
|
31
|
+
├── entities/entities.generated.ts # TypeScript DTOs
|
|
32
|
+
├── services/api/api.service.generated.ts # Typed API methods
|
|
33
|
+
├── components/base-details.generated.ts # Entity form components
|
|
34
|
+
├── services/validators/validators.generated.ts
|
|
35
|
+
└── enums/enums.generated.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Never modify `.generated.ts` files — they regenerate on build.
|
|
39
|
+
|
|
40
|
+
## Form System
|
|
41
|
+
|
|
42
|
+
### Inheritance Chain
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
BaseFormComponent<TMainUIForm, TSaveBody> (Spiderly library)
|
|
46
|
+
↓
|
|
47
|
+
{Entity}BaseDetailsComponent (generated)
|
|
48
|
+
↓
|
|
49
|
+
{Entity}DetailsComponent (your code)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Save Flow (execution order)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
1. onSave(rerouteToParentSlugAfterSave)
|
|
56
|
+
2. → Build saveBody from form raw values
|
|
57
|
+
3. → onBeforeSave(saveBody) ← mutate saveBody here
|
|
58
|
+
4. → baseFormService.isControlValid()
|
|
59
|
+
5. → saveObservableMethod(saveBody) (HTTP PUT)
|
|
60
|
+
6. → onAfterSaveRequest()
|
|
61
|
+
7. → Success toast + reroute
|
|
62
|
+
8. → onAfterSave()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Overridable Hooks
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
export class ProductDetailsComponent
|
|
69
|
+
extends BaseFormComponent<ProductMainUIForm, ProductSaveBody>
|
|
70
|
+
implements OnInit
|
|
71
|
+
{
|
|
72
|
+
override mainUIFormClass = ProductMainUIForm;
|
|
73
|
+
override saveBodyClass = ProductSaveBody;
|
|
74
|
+
|
|
75
|
+
override onBeforeSave = (saveBody?: ProductSaveBody) => {
|
|
76
|
+
saveBody.productDTO.stock =
|
|
77
|
+
saveBody.orderedProductVariantsSaveBodyDTO.reduce(
|
|
78
|
+
(sum, v) => sum + (v.productVariantDTO.stock ?? 0),
|
|
79
|
+
0,
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
override onAfterSave = () => {
|
|
84
|
+
this.refreshRelatedData();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
override rerouteToSavedObject = (rerouteId: number | string) => {
|
|
88
|
+
this.router.navigateByUrl(`/custom-path/${rerouteId}`);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Key Form Classes
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
SpiderlyFormControl<T> extends FormControl<T>
|
|
97
|
+
label: string // Untranslated name
|
|
98
|
+
labelForDisplay: string // Translated label
|
|
99
|
+
required: boolean
|
|
100
|
+
type: string // 'number', 'Date', 'Namebook[]'
|
|
101
|
+
validator: SpiderlyValidatorFn | null
|
|
102
|
+
|
|
103
|
+
SpiderlyFormGroup<T> extends FormGroup
|
|
104
|
+
controls: SpiderlyControlsOfType<T>
|
|
105
|
+
targetClass: SchemaAwareConstructor<T>
|
|
106
|
+
getControl(formControlName): SpiderlyFormControl
|
|
107
|
+
|
|
108
|
+
SpiderlyFormArray<T> extends FormArray
|
|
109
|
+
formGroupInitialValues: Partial<T>
|
|
110
|
+
targetClass: SchemaAwareConstructor<T>
|
|
111
|
+
getCrudMenuForOrderedData(): MenuItem[] // Remove, AddAbove, AddBelow
|
|
112
|
+
addNewFormGroup(index)
|
|
113
|
+
getFormGroups(): SpiderlyFormGroup<T>[]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### DTO Mapping
|
|
117
|
+
|
|
118
|
+
- `MainUIFormDTO` — what the API returns (read)
|
|
119
|
+
- `SaveBodyDTO` — what you send to API (write)
|
|
120
|
+
- `baseFormService.mapMainUIFormToSaveBody()` handles conversion automatically
|
|
121
|
+
- Naming convention: `orderedItemsMainUIFormDTO` → `orderedItemsSaveBodyDTO`
|
|
122
|
+
|
|
123
|
+
### Conditional visibility (`show*` inputs)
|
|
124
|
+
|
|
125
|
+
Every property block the generator emits into `{Entity}BaseDetailsComponent` is wrapped in `*ngIf="show{PropertyName}For{EntityName}"`, and each is exposed as an `@Input()` defaulting to `true`. Bind it from your `{Entity}DetailsComponent` template to show/hide a field — conditionally or statically — without editing generated code:
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<user-base-details
|
|
129
|
+
[parentFormGroup]="parentFormGroup"
|
|
130
|
+
[showIsDisabledForUser]="isAdmin" <!-- show a field only when a condition holds -->
|
|
131
|
+
[showEmailForUser]="false" <!-- hide a field outright -->
|
|
132
|
+
[showTimeOnBirthDateForUser]="true" <!-- calendar control: also render the time picker -->
|
|
133
|
+
(onSave)="onSave()"
|
|
134
|
+
></user-base-details>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Names are PascalCase: `show{PropertyName}For{EntityName}` (plus `showTimeOn{PropertyName}For{EntityName}` for calendar controls).
|
|
138
|
+
|
|
139
|
+
Library components/controls also expose their own `show*` `@Input()`s (e.g. `showLabel` on every control, `showAddButton` on the data table, `showPanelHeader` on the panel) — each a plain boolean, bound the same way: `[showAddButton]="canCreate"`. See the *UI Controls Reference* and *Presentational & Layout Components* tables below for the available inputs.
|
|
140
|
+
|
|
141
|
+
## Data Table
|
|
142
|
+
|
|
143
|
+
### Lazy Load Mode (server-side pagination, default)
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<spiderly-data-table
|
|
147
|
+
[cols]="cols"
|
|
148
|
+
[getPaginatedListObservableMethod]="getPaginatedProductListMethod"
|
|
149
|
+
[additionalFilterIdLong]="categoryId"
|
|
150
|
+
[navigateOnRowClick]="true"
|
|
151
|
+
[rowNavigationPath]="'/product-list'"
|
|
152
|
+
>
|
|
153
|
+
</spiderly-data-table>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Client-Side Mode
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<spiderly-data-table [cols]="cols" [items]="localItems" [hasLazyLoad]="false">
|
|
160
|
+
</spiderly-data-table>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Column Definition
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
cols: Column<ProductDTO>[] = [
|
|
167
|
+
new Column({ field: 'title', name: 'Title', filterType: 'text' }),
|
|
168
|
+
new Column({ field: 'price', name: 'Price', filterType: 'numeric', showMatchModes: true, decimalPlaces: 2 }),
|
|
169
|
+
new Column({ field: 'createdAt', name: 'Created', filterType: 'date', showTime: true }),
|
|
170
|
+
new Column({ field: 'isActive', name: 'Active', filterType: 'boolean' }),
|
|
171
|
+
new Column({ field: 'categoryDisplayName', name: 'Category', filterType: 'multiselect',
|
|
172
|
+
dropdownOrMultiselectValues: this.categoryOptions }),
|
|
173
|
+
new Column({
|
|
174
|
+
actions: [
|
|
175
|
+
new Action({ field: 'Details', icon: 'pi pi-pencil' }),
|
|
176
|
+
new Action({ field: 'Delete' }),
|
|
177
|
+
new Action({ field: 'custom', name: 'Clone', icon: 'pi pi-copy', onClick: (e) => this.clone(e.id) }),
|
|
178
|
+
]
|
|
179
|
+
}),
|
|
180
|
+
];
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The `onClick` callback receives an `ActionClickEvent` — `{ id, row, element, originalEvent }`. Use `element` (or `originalEvent`) to anchor an overlay/popover to the clicked action; `row` gives you the full row object. It fires only for custom `field` values (never `'Details'` / `'Delete'`).
|
|
184
|
+
|
|
185
|
+
### Key Inputs
|
|
186
|
+
|
|
187
|
+
| Input | Type | Default | Purpose |
|
|
188
|
+
| ---------------------------------- | ------------------------ | ------- | ----------------------- |
|
|
189
|
+
| `cols` | `Column[]` | — | Column definitions |
|
|
190
|
+
| `getPaginatedListObservableMethod` | `(filter) => Observable` | — | Server-side data source |
|
|
191
|
+
| `additionalFilterIdLong` | `number` | — | Parent entity filter |
|
|
192
|
+
| `hasLazyLoad` | `boolean` | `true` | Server vs client mode |
|
|
193
|
+
| `items` | `any[]` | — | Client-side data |
|
|
194
|
+
| `selectionMode` | `'single' \| 'multiple'` | — | Selection mode |
|
|
195
|
+
| `navigateOnRowClick` | `boolean` | `false` | Click row → details |
|
|
196
|
+
| `rowNavigationPath` | `string` | — | Base path for row click |
|
|
197
|
+
| `showAddButton` | `boolean` | `true` | Show "New" button |
|
|
198
|
+
| `showExportToExcelButton` | `boolean` | `true` | Show Excel export |
|
|
199
|
+
| `readonly` | `boolean` | `false` | Disable mutations |
|
|
200
|
+
|
|
201
|
+
### Key Outputs
|
|
202
|
+
|
|
203
|
+
| Output | Payload | Purpose |
|
|
204
|
+
| ----------------------- | --------------- | --------------------- |
|
|
205
|
+
| `onRowSelect` | `RowClickEvent` | Row selected |
|
|
206
|
+
| `onRowUnselect` | `RowClickEvent` | Row deselected |
|
|
207
|
+
| `onIsAllSelectedChange` | `AllClickEvent` | Select-all toggled |
|
|
208
|
+
| `onTotalRecordsChange` | `number` | Total records updated |
|
|
209
|
+
|
|
210
|
+
## Service Overrides
|
|
211
|
+
|
|
212
|
+
### ConfigServiceBase
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
@Injectable({ providedIn: "root" })
|
|
216
|
+
export class ConfigService extends ConfigServiceBase {
|
|
217
|
+
override logoPath = "assets/images/my-logo.png";
|
|
218
|
+
override companyName = "My Company";
|
|
219
|
+
override primaryColor = "#3B82F6";
|
|
220
|
+
override defaultPageSize = 25;
|
|
221
|
+
override loginSlug = "sign-in";
|
|
222
|
+
override showGoogleAuth = true;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Key properties: `apiUrl`, `frontendUrl`, `GoogleClientId`, `companyName`, `primaryColor`, `logoPath`, `defaultPageSize`, `loginSlug`, `showGoogleAuth`.
|
|
227
|
+
|
|
228
|
+
### AuthServiceBase
|
|
229
|
+
|
|
230
|
+
Override hooks for custom post-auth behavior:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
export class AuthService extends AuthServiceBase {
|
|
234
|
+
override onAfterLoginExternal = () => {
|
|
235
|
+
this.analyticsService.trackLogin("google");
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
override onAfterLogout = () => {
|
|
239
|
+
this.cacheService.clear();
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
override onAfterRefreshToken = () => {
|
|
243
|
+
this.syncPermissions();
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Key observables: `user$` (current user), `currentUserPermissionCodes$` (permission codes).
|
|
249
|
+
|
|
250
|
+
### LayoutServiceBase
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
export class LayoutService extends LayoutServiceBase {
|
|
254
|
+
override initTopBarData(): Observable<InitTopBarData> {
|
|
255
|
+
return this.apiService.getTopBarData().pipe(
|
|
256
|
+
map(data => new InitTopBarData({ ... }))
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Theme Configuration (AppConfig)
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
layoutConfig: AppConfig = {
|
|
266
|
+
inputStyle: "outlined", // 'outlined' | 'filled'
|
|
267
|
+
colorScheme: "light", // 'light' | 'dark'
|
|
268
|
+
menuMode: "static", // 'static' | 'overlay'
|
|
269
|
+
scale: 14, // Font scale
|
|
270
|
+
ripple: false,
|
|
271
|
+
theme: "lara-light-indigo",
|
|
272
|
+
color: "var(--p-primary-color)",
|
|
273
|
+
};
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Layout & Menu
|
|
277
|
+
|
|
278
|
+
### SpiderlyMenuItem
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
interface SpiderlyMenuItem extends PrimeNG.MenuItem {
|
|
282
|
+
hasPermission?: (permissionCodes: string[]) => boolean;
|
|
283
|
+
showPartnerDialog?: boolean;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Menu Setup
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// In layout.component.ts:
|
|
291
|
+
menu: SpiderlyMenuItem[] = [
|
|
292
|
+
{
|
|
293
|
+
label: this.translocoService.translate('Dashboard'),
|
|
294
|
+
icon: 'pi pi-fw pi-home',
|
|
295
|
+
routerLink: ['/dashboard'],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
label: this.translocoService.translate('Products'),
|
|
299
|
+
icon: 'pi pi-fw pi-box',
|
|
300
|
+
items: [
|
|
301
|
+
{ label: 'All Products', routerLink: ['/product-list'] },
|
|
302
|
+
{ label: 'Categories', routerLink: ['/category-list'] },
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Layout Template
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<!-- Side menu (default) -->
|
|
312
|
+
<spiderly-layout [menu]="menu" [isSideMenuLayout]="true">
|
|
313
|
+
<router-outlet></router-outlet>
|
|
314
|
+
</spiderly-layout>
|
|
315
|
+
|
|
316
|
+
<!-- Top menu -->
|
|
317
|
+
<spiderly-layout [menu]="menu" [isSideMenuLayout]="false">
|
|
318
|
+
<router-outlet></router-outlet>
|
|
319
|
+
</spiderly-layout>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Validation
|
|
323
|
+
|
|
324
|
+
### ValidatorAbstractService
|
|
325
|
+
|
|
326
|
+
Subclass to add custom validators per entity/field:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
@Injectable({ providedIn: "root" })
|
|
330
|
+
export class MyValidatorService extends ValidatorAbstractService {
|
|
331
|
+
setValidator(
|
|
332
|
+
control: SpiderlyFormControl,
|
|
333
|
+
className: string,
|
|
334
|
+
): SpiderlyValidatorFn {
|
|
335
|
+
if (className === "Product" && control.label === "sku") {
|
|
336
|
+
const validator: SpiderlyValidatorFn = (): ValidationErrors | null => {
|
|
337
|
+
const value = control.value as string;
|
|
338
|
+
if (value && !value.match(/^[A-Z0-9]{6,12}$/))
|
|
339
|
+
return { _: this.translocoService.translate("InvalidSKU") };
|
|
340
|
+
return null;
|
|
341
|
+
};
|
|
342
|
+
control.validator = validator;
|
|
343
|
+
}
|
|
344
|
+
return control.validator;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
setFormArrayValidator(formArray: SpiderlyFormArray, className: string): void {
|
|
348
|
+
if (className === "OrderItems") {
|
|
349
|
+
this.isFormArrayEmpty(formArray);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Built-in Validators
|
|
356
|
+
|
|
357
|
+
The built-in validators on `ValidatorAbstractService` (with signatures) are generated from the class: see [references/validators.generated.md](references/validators.generated.md).
|
|
358
|
+
|
|
359
|
+
## Translations
|
|
360
|
+
|
|
361
|
+
UI string translation — Transloco setup, `assets/i18n/{lang}.json` files, `translocoService.translate` / the `*transloco` template directive, and form label auto-translation (`getTranslatedLabel`) — is covered by the dedicated **frontend-localization** skill. For server-side (.NET) strings, see **backend-localization**.
|
|
362
|
+
|
|
363
|
+
## UI Controls Reference
|
|
364
|
+
|
|
365
|
+
Two generated references cover the controls:
|
|
366
|
+
|
|
367
|
+
- **Control type codes** — what you pass to `[UIControlType(nameof(UIControlTypeCodes.X))]`, and the property type each is auto-selected for: [references/ui-control-types.generated.md](references/ui-control-types.generated.md).
|
|
368
|
+
- **Control components** — each `spiderly-*` selector, its component class, control-specific `@Input()`s, and the shared `BaseControl` inputs: [references/controls.generated.md](references/controls.generated.md).
|
|
369
|
+
|
|
370
|
+
## Presentational & Layout Components
|
|
371
|
+
|
|
372
|
+
Reach for these before hand-rolling cards, panels, buttons, lists, or empty/loading states.
|
|
373
|
+
|
|
374
|
+
| Component | Selector | Purpose | Key inputs |
|
|
375
|
+
| ------------------- | -------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
376
|
+
| Card | `spiderly-card` | Titled content container with icon | `title`, `icon` |
|
|
377
|
+
| Panel | `spiderly-panel` | Collapsible section, supports multi-panel grouping + CRUD menu | `toggleable`, `collapsed`, `crudMenu`, `showRemoveIcon`, `isFirstMultiplePanel`/`isMiddleMultiplePanel`/`isLastMultiplePanel` |
|
|
378
|
+
| Panel parts | `panel-header`, `panel-body`, `panel-footer` | Compose a panel's regions | (slotted content) |
|
|
379
|
+
| Info card | `info-card` | Inline informational/callout box | `header`, `icon`, `showSmallIcon`, `textColor` |
|
|
380
|
+
| Index card | `index-card` | Ordered list item with index + CRUD menu | `index`, `header`, `description`, `crudMenu`, `showRemoveIcon`, `last` |
|
|
381
|
+
| Card skeleton | `card-skeleton` | **Loading placeholder** — use instead of a hand-rolled spinner/skeleton | `height` |
|
|
382
|
+
| Button | `spiderly-button` | Themed button | `type` (`button`/`submit`/`reset`) |
|
|
383
|
+
| Split button | `spiderly-split-button` | Button with dropdown menu | `dropdownItems` |
|
|
384
|
+
| Return button | `return-button` | Back-navigation button | `navigateUrl` |
|
|
385
|
+
| Data view | `spiderly-data-view` | Card/grid list with filters + pagination (vs. table) | `items`, `rows`, `filters`, `getPaginatedListObservableMethod`, `showCardWrapper` |
|
|
386
|
+
| Delete confirmation | `spiderly-delete-confirmation` | Standard delete-confirm dialog | — |
|
|
387
|
+
| Not found | `not-found` | **Empty / 404 state** — use instead of a hand-rolled empty state | — |
|
|
388
|
+
|
|
389
|
+
For data lists, use `spiderly-data-table` (tabular — see Data Table section) or `spiderly-data-view` (card/grid) — don't build either from scratch.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- GENERATED FROM framework-metadata.json — DO NOT EDIT.
|
|
2
|
+
Regenerate: `dotnet run --project Spiderly.MetadataExporter -- --out framework-metadata.json && node tools/extract-ts-metadata.mjs && node tools/gen-skill-docs.mjs` -->
|
|
3
|
+
|
|
4
|
+
# Form control components
|
|
5
|
+
|
|
6
|
+
Every control also accepts the shared `BaseControl` inputs: `control`, `controlValid`, `disabled`, `label`, `placeholder`, `showLabel`, `showRequired`, `showTooltip`, `tooltipIcon`, `tooltipText`.
|
|
7
|
+
|
|
8
|
+
| Selector | Component | Control-specific inputs |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `spiderly-autocomplete` | `SpiderlyAutocompleteComponent` | `appendTo`, `showClear`, `emptyMessage`, `displayName` |
|
|
11
|
+
| `spiderly-calendar` | `SpiderlyCalendarComponent` | `showTime`, `dateOnly`, `timeOnly` |
|
|
12
|
+
| `spiderly-checkbox` | `SpiderlyCheckboxComponent` | `fakeLabel`, `initializeToFalse`, `inlineLabel` |
|
|
13
|
+
| `spiderly-colorpicker` | `SpiderlyColorPickerComponent` | `showInputTextField` |
|
|
14
|
+
| `spiderly-dropdown` | `SpiderlyDropdownComponent` | `isBooleanPicker` |
|
|
15
|
+
| `spiderly-editor` | `SpiderlyEditorComponent` | `uploadImageMethod`, `objectId` |
|
|
16
|
+
| `spiderly-file` | `SpiderlyFileComponent` | `objectId`, `fileData`, `acceptedFileTypes`, `required`, `multiple`, `isUrlFileData`, `imageWidth`, `imageHeight`, `maxFileSize`, `files` |
|
|
17
|
+
| `spiderly-markdown` | `SpiderlyMarkdownComponent` | `uploadImageMethod`, `objectId` |
|
|
18
|
+
| `spiderly-multiautocomplete` | `SpiderlyMultiAutocompleteComponent` | — |
|
|
19
|
+
| `spiderly-multiselect` | `SpiderlyMultiSelectComponent` | — |
|
|
20
|
+
| `spiderly-number` | `SpiderlyNumberComponent` | `prefix`, `showButtons`, `decimal`, `maxFractionDigits` |
|
|
21
|
+
| `spiderly-password` | `SpiderlyPasswordComponent` | `showPasswordStrength` |
|
|
22
|
+
| `spiderly-textarea` | `SpiderlyTextareaComponent` | — |
|
|
23
|
+
| `spiderly-textbox` | `SpiderlyTextboxComponent` | `showButton`, `buttonIcon` |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- GENERATED FROM framework-metadata.json — DO NOT EDIT.
|
|
2
|
+
Regenerate: `dotnet run --project Spiderly.MetadataExporter -- --out framework-metadata.json && node tools/extract-ts-metadata.mjs && node tools/gen-skill-docs.mjs` -->
|
|
3
|
+
|
|
4
|
+
# Shared helper functions
|
|
5
|
+
|
|
6
|
+
Reusable helpers exported from `helper-functions.ts`. Import the one you need instead of re-implementing it.
|
|
7
|
+
|
|
8
|
+
| Signature | Description |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `ReflectProp(target: any, propertyKey: string)` | |
|
|
11
|
+
| `adjustColor(color: string, percent: number): string` | |
|
|
12
|
+
| `capitalizeFirstChar(str: string): string` | |
|
|
13
|
+
| `deleteAction(cols: Column[], actionField: string): void` | |
|
|
14
|
+
| `exportListToExcel(exportListToExcelObservableMethod: (filter: Filter) => Observable<any>, filter: Filter)` | |
|
|
15
|
+
| `firstCharToUpper(input: string): string` | |
|
|
16
|
+
| `getFileNameFromContentDisposition(resp: HttpResponse<Blob>, defaultName: string): string` | |
|
|
17
|
+
| `getHtmlImgDisplayString64(base64String: string)` | |
|
|
18
|
+
| `getImageDimensions(file: File): Promise<{ width: number; height: number }>` | |
|
|
19
|
+
| `getMimeTypeForFileName(fileName: string): string` | |
|
|
20
|
+
| `getMonth(numberOfTheMonth: number): string` | |
|
|
21
|
+
| `getParentUrl(currentUrl: string)` | |
|
|
22
|
+
| `getPrimengAutocompleteCodebookOptions(getAutocompleteListObservable: ( limit: number, query: string, ) => Observable<Codebook[]>, limit: number, query: string): Observable<PrimengOption[]>` | |
|
|
23
|
+
| `getPrimengAutocompleteNamebookOptions(getAutocompleteListObservable: ( limit: number, query: string, parentEntityId?: number, ) => Observable<Namebook[]>, limit: number, query: string, parentEntityId?: number): Observable<PrimengOption[]>` | |
|
|
24
|
+
| `getPrimengDropdownCodebookOptions(getDropdownListObservable: () => Observable<Codebook[]>): Observable<PrimengOption[]>` | |
|
|
25
|
+
| `getPrimengDropdownNamebookOptions(getDropdownListObservable: ( parentEntityId?: number, ) => Observable<Namebook[]>, parentEntityId?: number): Observable<PrimengOption[]>` | |
|
|
26
|
+
| `getPrimengNamebookOptions(namebookList: Namebook[]): PrimengOption[]` | |
|
|
27
|
+
| `isExcelFileType(mimeType: string): boolean` | |
|
|
28
|
+
| `isFileImageType(mimeType: string): boolean` | |
|
|
29
|
+
| `isNullOrEmpty(input: string)` | |
|
|
30
|
+
| `kebabToTitleCase(input: string): string` | |
|
|
31
|
+
| `nameOf<TObject extends { name: S }, S extends string>(funcOrClass: TObject): S` | |
|
|
32
|
+
| `nameof(key1: any, key2?: any): any` | |
|
|
33
|
+
| `parseDateOnlyLocal(s: string): Date \| null` | |
|
|
34
|
+
| `pushAction(cols: Column[], action: Action)` | |
|
|
35
|
+
| `selectedTab(tabs: SpiderlyTab[]): number` | |
|
|
36
|
+
| `singleOrDefault<T>(array: T[], predicate: (item: T) => boolean): T \| undefined` | |
|
|
37
|
+
| `splitPascalCase(input: string)` | |
|
|
38
|
+
| `toCommaSeparatedString<T>(input: T[]): string` | |
|
|
39
|
+
| `validatePrecisionScale(value: any, precision: number, scale: number, ignoreTrailingZeros: boolean): boolean` | |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!-- GENERATED FROM framework-metadata.json — DO NOT EDIT.
|
|
2
|
+
Regenerate: `dotnet run --project Spiderly.MetadataExporter -- --out framework-metadata.json && node tools/extract-ts-metadata.mjs && node tools/gen-skill-docs.mjs` -->
|
|
3
|
+
|
|
4
|
+
# UI control types
|
|
5
|
+
|
|
6
|
+
Defines the UI control types used by the Angular code generator to render form fields. Each value maps to a spiderly-* Angular component built on PrimeNG. Most types are automatically picked based on the property type, but you can override them with the [UIControlType] attribute:
|
|
7
|
+
|
|
8
|
+
| Name | Description |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `Decimal` | Numeric input for decimal/floating-point values. Renders spiderly-number with fraction digits enabled. Auto-detected for: decimal, decimal?, float, float?, double, double? |
|
|
11
|
+
| `File` | File upload control. Renders spiderly-file with support for image preview and dimension validation. Auto-detected for: properties decorated with any subclass of StorageAttribute (e.g. [DiskStorage], [S3PublicStorage], [S3PrivateStorage]). |
|
|
12
|
+
| `Dropdown` | Single-selection dropdown list. Renders spiderly-dropdown. Must be set explicitly via attribute. Commonly used for many-to-one navigation properties when you want a dropdown instead of the default autocomplete. |
|
|
13
|
+
| `TextArea` | Multi-line text input. Renders spiderly-textarea at full width. Must be set explicitly via attribute. Useful for longer plain-text content. |
|
|
14
|
+
| `Autocomplete` | Single-selection with search/autocomplete capability. Renders spiderly-autocomplete. Auto-detected for: many-to-one navigation properties (the generator creates a search method for this field). |
|
|
15
|
+
| `TextBox` | Single-line text input. Renders spiderly-textbox. Auto-detected for: string properties (default for strings). |
|
|
16
|
+
| `CheckBox` | Boolean toggle control. Renders spiderly-checkbox. Auto-detected for: bool, bool? |
|
|
17
|
+
| `Calendar` | Date/time picker. Renders spiderly-calendar with optional time selection. Auto-detected for: DateTime, DateTime? |
|
|
18
|
+
| `Integer` | Numeric input for whole numbers. Renders spiderly-number without fraction digits. Auto-detected for: int, int?, long, long?, byte, byte? |
|
|
19
|
+
| `ColorPicker` | Visual color picker. Renders spiderly-colorpicker with optional hex text input. Must be set explicitly via attribute. Stores the color as a hex string. |
|
|
20
|
+
| `Editor` | Rich text HTML editor. Renders spiderly-editor (Quill-based) at full width. Must be set explicitly via attribute. The value is stored as HTML. |
|
|
21
|
+
| `Markdown` | Markdown editor. Renders spiderly-markdown at full width — a plain textarea with a live "Preview" tab. The value is stored as raw Markdown text. Must be set explicitly via attribute. Pasting an image uploads it (when the property has an [S3PublicStorage] attribute) and inserts a standard  link; the preview is rendered with marked and is approximate vs. a consuming storefront's renderer. |
|
|
22
|
+
| `MultiAutocomplete` | Multi-selection with search/autocomplete capability. Renders spiderly-multiautocomplete at full width. Used for many-to-many relationships where items are selected via search. |
|
|
23
|
+
| `MultiSelect` | Multi-selection dropdown list. Renders spiderly-multiselect at full width. Used for many-to-many relationships where all options are shown in a dropdown. |
|
|
24
|
+
| `Password` | Masked password input. Renders spiderly-password with optional strength indicator. Must be set explicitly via attribute. |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- GENERATED FROM framework-metadata.json — DO NOT EDIT.
|
|
2
|
+
Regenerate: `dotnet run --project Spiderly.MetadataExporter -- --out framework-metadata.json && node tools/extract-ts-metadata.mjs && node tools/gen-skill-docs.mjs` -->
|
|
3
|
+
|
|
4
|
+
# Built-in validators
|
|
5
|
+
|
|
6
|
+
Built-in validators on `ValidatorAbstractService` (call from your `setValidator` / `setFormArrayValidator` override).
|
|
7
|
+
|
|
8
|
+
| Validator | Description |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `isArrayEmpty(control: SpiderlyFormControl): SpiderlyValidatorFn` | Validates that a SpiderlyFormControl holding an array value (e.g., multi-select dropdown) is not empty. |
|
|
11
|
+
| `isFormArrayEmpty(control: SpiderlyFormArray): void` | Validates that a SpiderlyFormArray (collection of form controls/groups) is not empty. |
|
|
12
|
+
| `notEmpty(control: SpiderlyFormControl): void` | |
|
|
13
|
+
| `validateImageDimensions(file: File, imageWidth: number, imageHeight: number): Promise<ImageDimensionsValidationResult>` | |
|