spiderly 19.8.4 → 19.8.6

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.
Files changed (33) hide show
  1. package/agent/docs/angular-customization/SKILL.md +389 -0
  2. package/agent/docs/angular-customization/references/controls.generated.md +23 -0
  3. package/agent/docs/angular-customization/references/helper-functions.generated.md +39 -0
  4. package/agent/docs/angular-customization/references/ui-control-types.generated.md +24 -0
  5. package/agent/docs/angular-customization/references/validators.generated.md +13 -0
  6. package/agent/docs/authorization/SKILL.md +385 -0
  7. package/agent/docs/authorization/references/api-error-codes.generated.md +17 -0
  8. package/agent/docs/authorization/references/security-endpoints.generated.md +24 -0
  9. package/agent/docs/backend-hooks/SKILL.md +231 -0
  10. package/agent/docs/backend-localization/SKILL.md +170 -0
  11. package/agent/docs/backend-testing/SKILL.md +65 -0
  12. package/agent/docs/custom-endpoints/SKILL.md +409 -0
  13. package/agent/docs/e2e-testing/SKILL.md +139 -0
  14. package/agent/docs/entity-design/SKILL.md +346 -0
  15. package/agent/docs/entity-design/references/attributes.generated.md +53 -0
  16. package/agent/docs/file-storage/SKILL.md +262 -0
  17. package/agent/docs/filtering-patterns/SKILL.md +127 -0
  18. package/agent/docs/filtering-patterns/references/match-mode-codes.generated.md +15 -0
  19. package/agent/docs/frontend-localization/SKILL.md +120 -0
  20. package/agent/docs/mapper-customization/SKILL.md +105 -0
  21. package/agent/manifest.json +34 -0
  22. package/agent/skills/add-entity/SKILL.md +158 -0
  23. package/agent/skills/deployment/SKILL.md +551 -0
  24. package/agent/skills/ef-migrations/SKILL.md +49 -0
  25. package/agent/skills/report-gap/SKILL.md +110 -0
  26. package/agent/skills/report-gap/scripts/build-issue-url.mjs +82 -0
  27. package/agent/skills/spiderly-upgrade/SKILL.md +166 -0
  28. package/agent/skills/verify-ui/SKILL.md +148 -0
  29. package/agent/skills/verify-ui/scripts/get-admin-token.mjs +134 -0
  30. package/fesm2022/spiderly.mjs +11 -6
  31. package/fesm2022/spiderly.mjs.map +1 -1
  32. package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +29 -3
  33. 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 ![](url) 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>` | |