osl-base-extended 0.1.19 → 1.0.2

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 CHANGED
@@ -1,64 +1,656 @@
1
- # OslBaseExtended
1
+ # osl-base-extended
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
3
+ > **Enterprise Angular UI toolkit** HTTP layer, CRUD UI, dynamic forms, skeleton loading, and 200+ utilities. Build data-driven pages in minutes, not days.
4
4
 
5
- ## Code scaffolding
5
+ [![npm version](https://img.shields.io/npm/v/osl-base-extended)](https://www.npmjs.com/package/osl-base-extended)
6
+ [![Angular](https://img.shields.io/badge/Angular-21%2B-red)](https://angular.dev)
7
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
8
+ [![Author](https://img.shields.io/badge/author-Bilal%20Raza-informational)](https://github.com/bilalraza052)
6
9
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
10
+ ---
11
+
12
+ ## What's Inside
13
+
14
+ | Module | What it does |
15
+ |--------|-------------|
16
+ | **`Httpbase`** | Abstract HTTP service — extend once per domain, get CRUD + auth + error handling free |
17
+ | **`<osl-setup>`** | Zero-config CRUD page: grid + add/edit dialog + delete confirm + search + pagination |
18
+ | **`<osl-dynamic-form>`** | Declarative forms — 12+ field types from a JSON config array |
19
+ | **`<osl-grid>`** | Data table with server-side or auto pagination, sorting, enum display |
20
+ | **`<osl-form-grid>`** | Inline editable grid with dynamic form elements per cell |
21
+ | **`[oslSkeleton]`** | GPU-accelerated skeleton directive — 8 layouts, 4 animations, 1 attribute |
22
+ | **`baseComponent`** | Inject once: `showSuccess()`, `showError()`, `openDialog()`, `openDeleteDialog()` |
23
+ | **`ArrayUtil`** | 25+ array helpers: chunk, groupBy, sortBy, paginate, intersection… |
24
+ | **`DateUtil`** | 40+ date helpers: format, diff, add/subtract, timeAgo, getAge… |
25
+ | **`NumberUtil`** | 30+ number helpers: formatCurrency, abbreviate, clamp, isPrime… |
26
+ | **`ObjectUtil`** | 25+ object helpers: deepClone, deepMerge, pick, omit, flattenObject… |
27
+ | **`StringUtil`** | 35+ string helpers: camelCase, slugify, truncate, mask, escapeHtml… |
28
+ | **`StorageUtil`** | localStorage, sessionStorage, and cookie helpers with JSON serialization |
29
+ | **`ValidationUtil`** | 40+ validators: email, URL, phone, password strength, credit card (Luhn)… |
30
+
31
+ ---
32
+
33
+ ## Installation
8
34
 
9
35
  ```bash
10
- ng generate component component-name
36
+ npm install osl-base-extended
11
37
  ```
12
38
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
39
+ ### Peer dependencies
14
40
 
15
41
  ```bash
16
- ng generate --help
42
+ npm install @angular/material
17
43
  ```
18
44
 
19
- ## Building
45
+ ### Add `HttpClientModule` to your app
20
46
 
21
- To build the library, run:
47
+ ```typescript
48
+ // app.config.ts
49
+ import { provideHttpClient } from '@angular/common/http';
22
50
 
23
- ```bash
24
- ng build osl-base-extended
51
+ export const appConfig: ApplicationConfig = {
52
+ providers: [provideHttpClient()]
53
+ };
25
54
  ```
26
55
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
56
+ ---
57
+
58
+ ## API Calling — Httpbase
28
59
 
29
- ### Publishing the Library
60
+ The core of the library. Create one service per backend controller by extending `Httpbase`.
30
61
 
31
- Once the project is built, you can publish your library by following these steps:
62
+ ### 1. Create a service
32
63
 
33
- 1. Navigate to the `dist` directory:
64
+ ```typescript
65
+ // user.service.ts
66
+ import { Injectable } from '@angular/core';
67
+ import { Httpbase } from 'osl-base-extended';
34
68
 
35
- ```bash
36
- cd dist/osl-base-extended
37
- ```
69
+ @Injectable({ providedIn: 'root' })
70
+ export class UserService extends Httpbase {
71
+ constructor() {
72
+ super('User'); // maps to /api/User/...
73
+ }
74
+ }
75
+ ```
38
76
 
39
- 2. Run the `npm publish` command to publish your library to the npm registry:
40
- ```bash
41
- npm publish
42
- ```
77
+ ### 2. Call built-in CRUD methods no extra code needed
43
78
 
44
- ## Running unit tests
79
+ ```typescript
80
+ // user.component.ts
81
+ export class UserComponent {
82
+ private userSvc = inject(UserService);
45
83
 
46
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
84
+ async loadAll() {
85
+ const res = await this.userSvc.getAll<User[]>();
86
+ if (res.isSuccessful) this.users = res.result;
87
+ else this.base.showError(res.error);
88
+ }
47
89
 
48
- ```bash
49
- ng test
90
+ async loadOne(id: number) {
91
+ const res = await this.userSvc.getById<User>(id);
92
+ }
93
+
94
+ async create(user: User) {
95
+ const res = await this.userSvc.save<User>(user);
96
+ }
97
+
98
+ async edit(user: User) {
99
+ const res = await this.userSvc.update<User>(user);
100
+ }
101
+
102
+ async remove(id: number) {
103
+ const res = await this.userSvc.remove(id);
104
+ }
105
+ }
50
106
  ```
51
107
 
52
- ## Running end-to-end tests
108
+ ### Built-in public methods
53
109
 
54
- For end-to-end (e2e) testing, run:
110
+ | Method | HTTP verb | Endpoint hit |
111
+ |--------|-----------|-------------|
112
+ | `getAll<T>()` | GET | `/api/{controller}/GetAll` |
113
+ | `getById<T>(id)` | GET | `/api/{controller}/GetById?id=…` |
114
+ | `save<T>(body)` | POST | `/api/{controller}/Save` |
115
+ | `update<T>(body)` | PUT | `/api/{controller}/Update` |
116
+ | `remove<T>(id)` | DELETE | `/api/{controller}/Delete?id=…` |
117
+ | `search(body)` | POST | `/api/{controller}/Search` |
118
+ | `getConfig()` | GET | `/api/{controller}/getConfig` |
55
119
 
56
- ```bash
57
- ng e2e
120
+ ### Custom endpoints — protected verb wrappers
121
+
122
+ ```typescript
123
+ @Injectable({ providedIn: 'root' })
124
+ export class UserService extends Httpbase {
125
+ constructor() { super('User'); }
126
+
127
+ // Custom POST
128
+ async changePassword(body: ChangePasswordDto) {
129
+ return this.post<void>('ChangePassword', body);
130
+ }
131
+
132
+ // Custom PUT
133
+ async updateRole(body: UpdateRoleDto) {
134
+ return this.put<User>('UpdateRole', body);
135
+ }
136
+
137
+ // Custom PATCH
138
+ async toggleActive(id: number) {
139
+ return this.patch<User>('ToggleActive', { id });
140
+ }
141
+
142
+ // Custom DELETE with query params
143
+ async bulkDelete(ids: number[]) {
144
+ return this.delete<void>('BulkDelete', [{ property: 'ids', value: ids }]);
145
+ }
146
+
147
+ // File upload (multipart/form-data)
148
+ async uploadAvatar(file: File) {
149
+ const fd = new FormData();
150
+ fd.append('file', file);
151
+ return this.upload<{ url: string }>('UploadAvatar', fd);
152
+ }
153
+ }
154
+ ```
155
+
156
+ ### Protected verb wrappers
157
+
158
+ | Method | HTTP verb | Notes |
159
+ |--------|-----------|-------|
160
+ | `post<T>(method, body)` | POST | JSON body |
161
+ | `get<T>(method, params?)` | GET | query params via `myParams[]` |
162
+ | `put<T>(method, body)` | PUT | JSON body |
163
+ | `patch<T>(method, body)` | PATCH | JSON body |
164
+ | `delete<T>(method, params?)` | DELETE | query params |
165
+ | `upload<T>(method, formData)` | POST multipart | 60 s timeout |
166
+
167
+ ### `HttpResponse<T>` — every method returns this
168
+
169
+ ```typescript
170
+ interface HttpResponse<T> {
171
+ isSuccessful: boolean; // true for 2xx
172
+ statusCode: number;
173
+ error: string; // human-readable error message
174
+ result: T; // response body
175
+ headers?: HttpHeaders;
176
+ }
177
+ ```
178
+
179
+ ### Authentication
180
+
181
+ The service reads `token` from `localStorage` and sends it as `Authorization: Bearer <token>` on every request automatically.
182
+
183
+ ```typescript
184
+ localStorage.setItem('token', 'your-jwt-here');
185
+ ```
186
+
187
+ ---
188
+
189
+ ## CRUD UI — `<osl-setup>`
190
+
191
+ The flagship component. One tag replaces a full page of boilerplate.
192
+
193
+ ### Template
194
+
195
+ ```html
196
+ <osl-setup
197
+ title="Users"
198
+ [columns]="columns"
199
+ [datasource]="users"
200
+ [formElements]="formElements"
201
+ [loading]="loading"
202
+ [isPaginated]="true"
203
+ [totalRecords]="totalRecords"
204
+ (onAdd)="onAdd()"
205
+ (onEdit)="onEdit($event)"
206
+ (onDelete)="onDelete($event)"
207
+ (onSave)="onSave($event)"
208
+ (onSearch)="onSearch($event)"
209
+ (pageChange)="onPageChange($event)"
210
+ />
211
+ ```
212
+
213
+ ### Component
214
+
215
+ ```typescript
216
+ export class UsersComponent {
217
+ loading = false;
218
+ users: User[] = [];
219
+ totalRecords = 0;
220
+
221
+ columns: OslGridColumn[] = [
222
+ { key: 'name', label: 'Name' },
223
+ { key: 'email', label: 'Email' },
224
+ { key: 'status', label: 'Status', enums: { 1: 'Active', 0: 'Inactive' } },
225
+ ];
226
+
227
+ formElements: elements[] = [
228
+ { key: 'name', label: 'Full Name', elementType: 'textbox', columns: 6, required: true },
229
+ { key: 'email', label: 'Email', elementType: 'textbox', columns: 6, required: true,
230
+ inputType: 'email' },
231
+ { key: 'status', label: 'Status', elementType: 'select', columns: 6,
232
+ datasource: [{ id: 1, name: 'Active' }, { id: 0, name: 'Inactive' }],
233
+ displayField: 'name', valueField: 'id' },
234
+ ];
235
+
236
+ async onSave(e: { model: User; mode: 'add' | 'edit' }) {
237
+ const res = e.mode === 'add'
238
+ ? await this.userSvc.save(e.model)
239
+ : await this.userSvc.update(e.model);
240
+ if (res.isSuccessful) this.loadUsers();
241
+ else this.base.showError(res.error);
242
+ }
243
+
244
+ async onDelete(user: User) {
245
+ const res = await this.userSvc.remove(user.id);
246
+ if (res.isSuccessful) this.loadUsers();
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### `<osl-setup>` inputs
252
+
253
+ | Input | Type | Default | Description |
254
+ |-------|------|---------|-------------|
255
+ | `title` | `string` | — | Entity name shown in heading and dialog |
256
+ | `columns` | `OslGridColumn[]` | — | Column definitions |
257
+ | `datasource` | `any[]` | — | Table row data |
258
+ | `formElements` | `elements[]` | — | Dynamic form configuration |
259
+ | `loading` | `boolean` | `false` | Shows skeleton rows while loading |
260
+ | `isPaginated` | `boolean` | `false` | Enable pagination footer |
261
+ | `pageSize` | `number` | `25` | Rows per page |
262
+ | `totalRecords` | `number` | — | Total count for server-side pagination |
263
+ | `autoMode` | `boolean` | `true` | Library handles client-side sort/page |
264
+ | `tableHeight` | `string` | — | CSS height for scrollable table body |
265
+ | `dialogWidth` | `string` | `'50vw'` | Width of add/edit dialog |
266
+ | `isLister` | `boolean` | `false` | Hides actions column |
267
+ | `beforeDisplay` | `(model) => any` | — | Transform model before opening dialog |
268
+ | `onAddEditFn` | `(model, mode) => void` | — | Override default add/edit dialog |
269
+
270
+ ### `<osl-setup>` outputs
271
+
272
+ | Output | Payload | When |
273
+ |--------|---------|------|
274
+ | `onAdd` | — | Add button clicked |
275
+ | `onEdit` | row object | Edit button clicked |
276
+ | `onDelete` | row object | Delete confirmed |
277
+ | `onSave` | `{ model, mode }` | Dialog save button clicked |
278
+ | `onSearch` | `string` | Search input changes |
279
+ | `pageChange` | `OslPageEvent` | Page number changes |
280
+ | `pageSizeChange` | `number` | Page size changes |
281
+ | `sortChange` | `OslSortEvent` | Column header clicked |
282
+ | `onRowClick` | row object | Row clicked |
283
+
284
+ ---
285
+
286
+ ## Dynamic Forms — `<osl-dynamic-form>`
287
+
288
+ Build any form from a plain array — no template code needed.
289
+
290
+ ```html
291
+ <osl-dynamic-form
292
+ [elements]="formElements"
293
+ [(model)]="formModel"
294
+ [skeletonLoading]="loading"
295
+ />
296
+ ```
297
+
298
+ ### Element types
299
+
300
+ | `elementType` | Renders |
301
+ |---------------|---------|
302
+ | `textbox` | `<osl-input>` — text, password, email, number, tel, url |
303
+ | `textarea` | `<osl-textarea>` — resizable, character counter |
304
+ | `select` | `<osl-select>` — static or API datasource |
305
+ | `autocomplete` | `<osl-autocomplete>` — local or API search with lister |
306
+ | `radio` | `<osl-radio>` — horizontal/vertical layout |
307
+ | `checkbox` | `<osl-checkbox>` — with indeterminate support |
308
+ | `slide-toggle` | `<osl-slide-toggle>` — custom true/false labels |
309
+ | `datepicker` | `<osl-datepicker>` — date, datetime-local, time, month, week |
310
+ | `file-uploader` | `<osl-file-upload>` — drag & drop, size validation |
311
+ | `button` | `<osl-button>` — all variants and sizes |
312
+ | `fieldset` | Nested group of elements |
313
+ | `templateRef` | Inject a custom `TemplateRef` |
314
+
315
+ ### Common element options
316
+
317
+ ```typescript
318
+ {
319
+ key: 'fieldName', // maps to model property
320
+ label: 'Display Label',
321
+ elementType: 'textbox',
322
+ columns: 6, // 1–12 grid columns (Bootstrap-style)
323
+ required: true,
324
+ requiredIf: (m) => m.type === 'enterprise',
325
+ hideIf: (m) => !m.showField,
326
+ disabledIf: () => !hasPermission,
327
+ change: (model) => { /* react to value change */ },
328
+
329
+ // select / autocomplete
330
+ datasource: [],
331
+ displayField: 'name',
332
+ valueField: 'id',
333
+
334
+ // API-backed datasource
335
+ apiService: inject(CategoryService),
336
+ apiMethod: 'getAll',
337
+
338
+ // textbox extras
339
+ inputType: 'email',
340
+ placeholder: 'Enter email',
341
+ mask: '(000) 000-0000',
342
+ prefixIcon: 'person',
343
+ suffixIcon: 'clear',
344
+ maxLength: 100,
345
+ }
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Data Grid — `<osl-grid>`
351
+
352
+ ```html
353
+ <osl-grid
354
+ [columns]="columns"
355
+ [datasource]="rows"
356
+ [isPaginated]="true"
357
+ [totalRecords]="total"
358
+ [loading]="loading"
359
+ tableHeight="400px"
360
+ (editClick)="onEdit($event)"
361
+ (deleteClick)="onDelete($event)"
362
+ (pageChange)="loadPage($event)"
363
+ (sortChange)="onSort($event)"
364
+ />
365
+ ```
366
+
367
+ ### `OslGridColumn` interface
368
+
369
+ ```typescript
370
+ interface OslGridColumn {
371
+ key: string; // model property
372
+ label: string; // header text
373
+ enums?: Record<any, string>; // value → display label map
374
+ displayFn?: (row: any) => string; // custom cell renderer
375
+ isActions?: boolean; // marks the edit/delete column
376
+ }
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Skeleton Loading — `[oslSkeleton]`
382
+
383
+ Drop one attribute on any element.
384
+
385
+ ```html
386
+ <!-- Auto-detects child structure -->
387
+ <div [oslSkeleton]="loading">...</div>
388
+
389
+ <!-- Explicit types -->
390
+ <table [oslSkeleton]="loading" oslSkeletonType="table"
391
+ [oslSkeletonTableRows]="8" [oslSkeletonTableCols]="4">
392
+ </table>
393
+
394
+ <ul [oslSkeleton]="loading" oslSkeletonType="list" [oslSkeletonListItems]="5"></ul>
395
+
396
+ <div [oslSkeleton]="loading" oslSkeletonType="card"></div>
397
+
398
+ <img [oslSkeleton]="loading" oslSkeletonType="circle" oslSkeletonCircleSize="48px">
399
+ ```
400
+
401
+ ### Key inputs
402
+
403
+ | Input | Type | Default | Description |
404
+ |-------|------|---------|-------------|
405
+ | `oslSkeleton` | `boolean` | — | Toggle on/off |
406
+ | `oslSkeletonType` | `'auto'│'text'│'rect'│'circle'│'card'│'list'│'table'│'avatar-text'` | `'auto'` | Layout preset |
407
+ | `oslSkeletonAnimation` | `'shimmer'│'pulse'│'wave'│'none'` | `'shimmer'` | Animation style |
408
+ | `oslSkeletonTheme` | `'light'│'dark'` | `'light'` | Color theme |
409
+ | `oslSkeletonRows` | `number` | `3` | Text rows count |
410
+ | `oslSkeletonTableRows` | `number` | `5` | Table row count |
411
+ | `oslSkeletonTableCols` | `number` | `4` | Table column count |
412
+ | `oslSkeletonListItems` | `number` | `4` | List item count |
413
+ | `oslSkeletonDuration` | `number` | `1500` | Animation ms |
414
+ | `oslSkeletonDelay` | `number` | `0` | Delay before showing |
415
+
416
+ ### Global theme
417
+
418
+ ```typescript
419
+ // Set once in a root component
420
+ inject(OslSkeletonThemeService).setTheme('dark');
421
+ ```
422
+
423
+ ---
424
+
425
+ ## `baseComponent` — UI Utilities
426
+
427
+ ```typescript
428
+ export class MyComponent {
429
+ private base = inject(baseComponent);
430
+
431
+ showFeedback(res: HttpResponse<any>) {
432
+ if (res.isSuccessful) this.base.showSuccess('Saved!');
433
+ else this.base.showError(res.error);
434
+ }
435
+
436
+ openCustomDialog() {
437
+ this.base.openDialog(
438
+ 'Edit User',
439
+ MyFormComponent, // formBody
440
+ MyFooterComponent, // formFooter
441
+ '40vw',
442
+ { userId: 1 } // data passed to dialog
443
+ );
444
+ }
445
+
446
+ confirmDelete(item: any) {
447
+ this.base.openDeleteDialog(
448
+ `Delete "${item.name}"?`,
449
+ 'Confirm Delete',
450
+ 'Yes, Delete',
451
+ 'Cancel',
452
+ item
453
+ );
454
+ }
455
+ }
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Utility Namespaces
461
+
462
+ Import by namespace — tree-shakable pure functions, no external dependencies.
463
+
464
+ ```typescript
465
+ import { ArrayUtil, DateUtil, NumberUtil, ObjectUtil,
466
+ StringUtil, StorageUtil, ValidationUtil } from 'osl-base-extended';
467
+ ```
468
+
469
+ ### ArrayUtil
470
+
471
+ ```typescript
472
+ ArrayUtil.chunk([1,2,3,4,5], 2) // [[1,2],[3,4],[5]]
473
+ ArrayUtil.unique([1,1,2,3,2]) // [1,2,3]
474
+ ArrayUtil.uniqueBy(users, 'email')
475
+ ArrayUtil.groupBy(orders, 'status')
476
+ ArrayUtil.sortBy(items, 'price', 'asc')
477
+ ArrayUtil.filterBy(items, 'category', 'Electronics')
478
+ ArrayUtil.paginate(items, 2, 10) // page 2, 10 per page
479
+ ArrayUtil.flatten([[1,[2]],3])
480
+ ArrayUtil.sumBy(cart, 'total')
481
+ ArrayUtil.intersection([1,2,3],[2,3,4]) // [2,3]
482
+ ArrayUtil.difference([1,2,3],[2,3]) // [1]
483
+ ArrayUtil.toggle(selected, item) // add if missing, remove if present
484
+ ArrayUtil.shuffle(deck)
485
+ ArrayUtil.sample(pool, 3) // random 3
486
+ ArrayUtil.zip(['a','b'],[1,2]) // [['a',1],['b',2]]
487
+ ```
488
+
489
+ ### DateUtil
490
+
491
+ ```typescript
492
+ DateUtil.formatDate(new Date(), 'YYYY-MM-DD')
493
+ DateUtil.timeAgo(pastDate) // '3 hours ago'
494
+ DateUtil.getAge(birthDate) // 28
495
+ DateUtil.addDays(date, 7)
496
+ DateUtil.addMonths(date, 3)
497
+ DateUtil.diffInDays(dateA, dateB)
498
+ DateUtil.diffInMinutes(dateA, dateB)
499
+ DateUtil.isBefore(dateA, dateB)
500
+ DateUtil.isToday(date)
501
+ DateUtil.isWeekend(date)
502
+ DateUtil.startOfMonth(date)
503
+ DateUtil.endOfMonth(date)
504
+ DateUtil.nextWorkday(date)
505
+ DateUtil.getWeekNumber(date)
506
+ DateUtil.isLeapYear(2024) // true
507
+ ```
508
+
509
+ ### NumberUtil
510
+
511
+ ```typescript
512
+ NumberUtil.formatCurrency(1500.5, 'USD') // '$1,500.50'
513
+ NumberUtil.abbreviate(1500000) // '1.5M'
514
+ NumberUtil.toOrdinal(3) // '3rd'
515
+ NumberUtil.clamp(value, 0, 100)
516
+ NumberUtil.percentage(part, total)
517
+ NumberUtil.round(3.14159, 2) // 3.14
518
+ NumberUtil.randomInt(1, 100)
519
+ NumberUtil.isPrime(17) // true
520
+ NumberUtil.fibonacci(10) // 55
521
+ NumberUtil.lerp(0, 100, 0.5) // 50
522
+ ```
523
+
524
+ ### ObjectUtil
525
+
526
+ ```typescript
527
+ ObjectUtil.deepClone(obj)
528
+ ObjectUtil.deepMerge(defaults, overrides)
529
+ ObjectUtil.pick(user, ['name', 'email'])
530
+ ObjectUtil.omit(user, ['password'])
531
+ ObjectUtil.flattenObject({ a: { b: 1 } }) // { 'a.b': 1 }
532
+ ObjectUtil.unflattenObject({ 'a.b': 1 }) // { a: { b: 1 } }
533
+ ObjectUtil.getPath(obj, 'user.address.city')
534
+ ObjectUtil.setPath(obj, 'user.role', 'admin')
535
+ ObjectUtil.toQueryString({ page: 1, q: 'test' }) // 'page=1&q=test'
536
+ ObjectUtil.diff(objA, objB) // changed keys
537
+ ObjectUtil.isEqual(a, b) // deep equality
538
+ ```
539
+
540
+ ### StringUtil
541
+
542
+ ```typescript
543
+ StringUtil.camelCase('hello world') // 'helloWorld'
544
+ StringUtil.pascalCase('hello world') // 'HelloWorld'
545
+ StringUtil.snakeCase('helloWorld') // 'hello_world'
546
+ StringUtil.kebabCase('Hello World') // 'hello-world'
547
+ StringUtil.titleCase('hello world') // 'Hello World'
548
+ StringUtil.toSlug('Hello World!') // 'hello-world'
549
+ StringUtil.truncate('Long text...', 20)
550
+ StringUtil.mask('4111111111111111', 4) // '************1111'
551
+ StringUtil.initials('Bilal Raza') // 'BR'
552
+ StringUtil.escapeHtml('<script>') // '&lt;script&gt;'
553
+ StringUtil.stripHtml('<p>Hello</p>') // 'Hello'
554
+ StringUtil.randomString(16)
555
+ StringUtil.isPalindrome('racecar') // true
556
+ ```
557
+
558
+ ### StorageUtil
559
+
560
+ ```typescript
561
+ // localStorage
562
+ StorageUtil.setLocal('user', { id: 1, name: 'Bilal' });
563
+ StorageUtil.getLocal<User>('user');
564
+ StorageUtil.removeLocal('user');
565
+
566
+ // sessionStorage
567
+ StorageUtil.setSession('draft', formData);
568
+ StorageUtil.getSession<Draft>('draft');
569
+
570
+ // Cookies
571
+ StorageUtil.setCookie('lang', 'en', 30); // expires in 30 days
572
+ StorageUtil.getCookie('lang');
573
+ StorageUtil.removeCookie('lang');
574
+ ```
575
+
576
+ ### ValidationUtil
577
+
578
+ ```typescript
579
+ ValidationUtil.isEmail('user@example.com') // true
580
+ ValidationUtil.isUrl('https://example.com') // true
581
+ ValidationUtil.isPhone('+1-800-555-0100')
582
+ ValidationUtil.isCreditCard('4111111111111111') // Luhn check
583
+ ValidationUtil.isIPv4('192.168.1.1')
584
+ ValidationUtil.isStrongPassword('P@ssw0rd!', {
585
+ minLength: 8,
586
+ requireUppercase: true,
587
+ requireSpecial: true,
588
+ })
589
+ ValidationUtil.passwordStrength('P@ssw0rd!') // 0–5 score
590
+ ValidationUtil.isJSON('{"key":"val"}')
591
+ ValidationUtil.isHexColor('#ff5500')
592
+ ValidationUtil.matchesPattern(value, /^\d{4}$/)
593
+ ```
594
+
595
+ ---
596
+
597
+ ## Module Import
598
+
599
+ ```typescript
600
+ // app.module.ts (or standalone app)
601
+ import { FormStructureModule, OslSkeletonModule } from 'osl-base-extended';
602
+
603
+ @NgModule({
604
+ imports: [
605
+ FormStructureModule,
606
+ OslSkeletonModule,
607
+ ]
608
+ })
609
+ export class AppModule {}
610
+ ```
611
+
612
+ ---
613
+
614
+ ## Interfaces Reference
615
+
616
+ ```typescript
617
+ // HTTP
618
+ interface HttpResponse<T> {
619
+ isSuccessful: boolean;
620
+ statusCode: number;
621
+ error: string;
622
+ result: T;
623
+ headers?: HttpHeaders;
624
+ }
625
+
626
+ interface myParams {
627
+ property: string;
628
+ value: any;
629
+ }
630
+
631
+ // Grid
632
+ interface OslGridColumn {
633
+ key: string;
634
+ label: string;
635
+ enums?: Record<any, string>;
636
+ displayFn?: (row: any) => string;
637
+ isActions?: boolean;
638
+ }
639
+
640
+ interface OslPageEvent { page: number; pageSize: number; }
641
+ interface OslSortEvent { key: string; direction: 'asc' | 'desc'; }
642
+
643
+ // Form
644
+ type InputType = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
645
+ type DateInputType = 'date' | 'datetime-local' | 'time' | 'month' | 'week';
646
+ type TextareaResize = 'none' | 'both' | 'horizontal' | 'vertical';
647
+ type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
648
+ | 'outline-primary' | 'outline-secondary' | 'icon';
649
+ type ButtonSize = 'sm' | 'md' | 'lg';
58
650
  ```
59
651
 
60
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
652
+ ---
61
653
 
62
- ## Additional Resources
654
+ ## License
63
655
 
64
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
656
+ ISC © Bilal Raza