@ogidor/dashboard 1.0.6 → 1.0.8

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,20 +1,38 @@
1
1
  # @ogidor/dashboard
2
2
 
3
- A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
4
- Drop it into any project, feed it your own card content, and get a fully functional multi-workspace grid with persistent layout, pop-out windows, and live cross-tab sync — out of the box.
3
+ A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
4
+ Drop it into any project, supply your own card content, and get a fully featured
5
+ multi-workspace grid with persistent layout, pop-out windows, and live cross-tab
6
+ sync — out of the box.
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Features](#features)
13
+ 2. [Installation](#installation)
14
+ 3. [Quick Start](#quick-start)
15
+ 4. [API Reference](#api-reference)
16
+ 5. [Models](#models)
17
+ 6. [Theming](#theming)
18
+ 7. [Pop-out Windows](#pop-out-windows)
19
+ 8. [Persisting Layouts](#persisting-layouts)
20
+ 9. [Full Integration Example](#full-integration-example)
21
+ 10. [Development](#development)
22
+ 11. [License](#license)
5
23
 
6
24
  ---
7
25
 
8
26
  ## Features
9
27
 
10
- - **Drag & Drop Grid** — smooth cursor-following drag with animated collision resolution and auto-compaction
11
- - **Resize** — live resize with a bottom-right handle; other cards shift out of the way in real time
12
- - **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent widget layout
13
- - **Independent Pop-out Layouts** — each window keeps its own card positions; drag cards in Window B and Window A is completely unaffected
14
- - **Structural Sync** — adding, removing, or editing a widget in any window immediately appears in all other open windows
15
- - **Persistent Layout** — layout is saved to `localStorage` and restored on reload
16
- - **Content-Agnostic Cards** — you own the card body; render any Angular component, chart, or HTML inside
17
- - **Fully Themeable** — customise every colour (backgrounds, tabs, cards, buttons, handles, placeholders) and font via a `DashboardTheme` input or CSS custom properties
28
+ - **Drag & Drop Grid** — smooth cursor-following drag with collision resolution and auto-compaction.
29
+ - **Live Resize** — bottom-right resize handle; surrounding cards shift out of the way in real time.
30
+ - **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent layout.
31
+ - **Independent Pop-out Layouts** — pop a workspace into its own window; dragging there never affects the main window.
32
+ - **Structural Sync** — adding, removing, or renaming a widget in any window instantly appears in all open windows via `BroadcastChannel`.
33
+ - **Persistent Layout** — state is saved to `localStorage` on every change and restored on reload.
34
+ - **Content-Agnostic Cards** — you own the card body; stamp any Angular component, chart, or markup inside via `ng-template`.
35
+ - **Fully Themeable** — 20 colour tokens plus font family, controllable via a typed `DashboardTheme` input or CSS custom properties.
18
36
 
19
37
  ---
20
38
 
@@ -24,7 +42,7 @@ Drop it into any project, feed it your own card content, and get a fully functio
24
42
  npm install @ogidor/dashboard line-awesome
25
43
  ```
26
44
 
27
- Add the Line Awesome icon font to your global styles (`angular.json` or `styles.scss`):
45
+ Add Line Awesome to your global styles (`styles.scss` or `angular.json`):
28
46
 
29
47
  ```scss
30
48
  @import "line-awesome/dist/line-awesome/css/line-awesome.css";
@@ -34,7 +52,7 @@ Add the Line Awesome icon font to your global styles (`angular.json` or `styles.
34
52
 
35
53
  ## Quick Start
36
54
 
37
- ### 1. Import the module
55
+ ### 1. Import `DashboardModule`
38
56
 
39
57
  ```typescript
40
58
  import { NgModule } from '@angular/core';
@@ -48,14 +66,14 @@ import { DashboardModule } from '@ogidor/dashboard';
48
66
  export class AppModule {}
49
67
  ```
50
68
 
51
- ### 2. Use the component
69
+ ### 2. Add the component to your template
52
70
 
53
71
  ```html
54
72
  <app-dashboard
55
73
  (addWidgetRequested)="onAddWidget()"
56
74
  (editWidgetRequested)="onEditWidget($event)"
57
75
  >
58
- <!-- Optional: custom card body rendered inside every widget -->
76
+ <!-- Stamp your own content inside every card -->
59
77
  <ng-template gridCell let-widget="widget">
60
78
  <div style="padding: 8px; color: white;">
61
79
  {{ widget.data | json }}
@@ -64,32 +82,28 @@ export class AppModule {}
64
82
  </app-dashboard>
65
83
  ```
66
84
 
67
- ### 3. Add widgets programmatically
68
-
69
- Inject `DashboardStateService` anywhere in your app:
85
+ ### 3. Add and edit widgets programmatically
70
86
 
71
87
  ```typescript
72
88
  import { DashboardStateService, Widget } from '@ogidor/dashboard';
73
89
 
74
- @Component({ ... })
75
90
  export class AppComponent {
76
91
  constructor(private dashboard: DashboardStateService) {}
77
92
 
78
93
  onAddWidget() {
79
94
  this.dashboard.addWidget({
80
- title: 'Sales Overview',
95
+ title: 'Revenue',
81
96
  cols: 6,
82
97
  rows: 4,
83
- cardColor: '#1a1a2e', // optional — overrides the theme card color
84
- data: { metric: 'revenue', period: 'Q1' }, // any shape you need
98
+ cardColor: '#1a1a2e', // optional — overrides the global card color
99
+ data: { metric: 'revenue' },
85
100
  });
86
101
  }
87
102
 
88
103
  onEditWidget(widget: Widget) {
89
- // open your own dialog, then:
104
+ // open your own dialog, then patch:
90
105
  this.dashboard.updateWidget(widget.id, {
91
106
  title: 'Updated Title',
92
- cardColor: '#0f3460',
93
107
  data: { metric: 'users' },
94
108
  });
95
109
  }
@@ -100,34 +114,36 @@ export class AppComponent {
100
114
 
101
115
  ## API Reference
102
116
 
103
- ### `<app-dashboard>`
117
+ ### `app-dashboard`
118
+
119
+ The main component. Handles tabs, the Add Widget button, and the grid.
104
120
 
105
121
  #### Inputs
106
122
 
107
- | Input | Type | Default | Description |
108
- |---|---|---|---|
109
- | `initialLayout` | `string` | | JSON string from a previous `serializeLayout()` call. Restores a full layout on load. |
110
- | `theme` | `DashboardTheme` | See below | Override default colors and font. |
123
+ | Input | Type | Description |
124
+ |---|---|---|
125
+ | `theme` | `DashboardTheme` | Override any colour token or font. All fields optional. |
126
+ | `initialLayout` | `string` | JSON from a previous `serializeLayout()` call. Restores the full layout on load. |
111
127
 
112
128
  #### Outputs
113
129
 
114
130
  | Output | Payload | Description |
115
131
  |---|---|---|
116
132
  | `addWidgetRequested` | `void` | Fires when the user clicks **Add Widget**. Open your own dialog here. |
117
- | `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. Receives the full widget object. |
133
+ | `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. |
118
134
 
119
135
  #### Content children
120
136
 
121
137
  | Selector | Description |
122
138
  |---|---|
123
- | `<ng-template gridCell let-widget="widget">` | Optional template rendered inside every card body. The `widget` context variable gives you the full `Widget` object including your `data`. |
139
+ | `ng-template[gridCell]` | Rendered inside every card body. Template context exposes `widget: Widget`. |
124
140
 
125
141
  #### Methods (via `@ViewChild`)
126
142
 
127
143
  ```typescript
128
144
  @ViewChild(DashboardComponent) dash!: DashboardComponent;
129
145
 
130
- // Serialize the current layout to a JSON string (e.g. to save to a backend)
146
+ // Returns the current layout as a JSON string
131
147
  const json = this.dash.serializeLayout();
132
148
  ```
133
149
 
@@ -135,7 +151,7 @@ const json = this.dash.serializeLayout();
135
151
 
136
152
  ### `DashboardStateService`
137
153
 
138
- The service is provided at root level. Inject it directly for full programmatic control.
154
+ Provided at root. Inject it anywhere for full programmatic control.
139
155
 
140
156
  ```typescript
141
157
  constructor(private state: DashboardStateService) {}
@@ -145,14 +161,15 @@ constructor(private state: DashboardStateService) {}
145
161
 
146
162
  | Method | Signature | Description |
147
163
  |---|---|---|
148
- | `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Adds a widget to the active workspace. Returns the created widget with its generated `id`. |
149
- | `updateWidget` | `(id: string, patch: Partial<Widget>) => void` | Update any widget field except `id`, `x`, `y`, `cols`, `rows`. |
164
+ | `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Add a widget to the active workspace. Returns the new widget with its generated `id`. |
165
+ | `updateWidget` | `(id: string, patch: Partial<Omit<Widget, 'id'&#124;'x'&#124;'y'&#124;'cols'&#124;'rows'>>) => void` | Update title, cardColor, or data. Use `updateWidgetPosition` for positional fields. |
150
166
  | `removeWidget` | `(id: string) => void` | Remove a widget from the active workspace. |
151
- | `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition/resize a widget. |
167
+ | `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition or resize a widget. |
168
+ | `getActivePage` | `() => Page \| undefined` | Returns the currently active `Page` object. |
152
169
  | `addPage` | `(name: string) => void` | Add a new workspace tab. |
153
- | `removePage` | `(id: string) => void` | Remove a workspace tab (minimum 1 kept). |
154
- | `setActivePage` | `(id: string) => void` | Switch to a workspace by id. |
155
- | `popOutPage` | `(id: string) => void` | Open a workspace in its own browser window. |
170
+ | `removePage` | `(id: string) => void` | Remove a workspace tab (at least one is always kept). |
171
+ | `setActivePage` | `(id: string) => void` | Switch the active workspace by id. |
172
+ | `popOutPage` | `(id: string) => void` | Open a workspace in a separate browser window. |
156
173
  | `loadLayout` | `(config: DashboardConfig) => void` | Load a full layout object (parsed from `serializeLayout`). |
157
174
  | `serializeLayout` | `() => string` | Serialize the full layout to a JSON string. |
158
175
 
@@ -160,73 +177,163 @@ constructor(private state: DashboardStateService) {}
160
177
 
161
178
  | Observable | Type | Description |
162
179
  |---|---|---|
163
- | `pages$` | `Observable<Page[]>` | Emits the full page list on every change. |
164
- | `activePageId$` | `Observable<string>` | Emits the active workspace id on every change. |
180
+ | `pages$` | `Observable<Page[]>` | Emits whenever the page list changes. |
181
+ | `activePageId$` | `Observable<string>` | Emits whenever the active workspace changes. |
182
+
183
+ ---
184
+
185
+ ### `app-grid` (Advanced)
186
+
187
+ The raw grid engine, exported for advanced use cases. `app-dashboard` uses this internally.
188
+
189
+ ```html
190
+ <app-grid
191
+ [widgets]="myWidgets"
192
+ [columns]="12"
193
+ [gap]="16"
194
+ [rowHeight]="80"
195
+ (itemChanged)="onItemChanged($event)"
196
+ (layoutChanged)="onLayoutChanged($event)"
197
+ >
198
+ <ng-template gridCell let-widget="widget">
199
+ <!-- your card here -->
200
+ </ng-template>
201
+ </app-grid>
202
+ ```
203
+
204
+ #### Inputs
205
+
206
+ | Input | Type | Default | Description |
207
+ |---|---|---|---|
208
+ | `widgets` | `Widget[]` | `[]` | The widgets to render. |
209
+ | `columns` | `number` | `12` | Number of grid columns. |
210
+ | `gap` | `number` | `16` | Gap between cells in pixels. |
211
+ | `rowHeight` | `number` | `80` | Height of one grid row in pixels. |
212
+ | `minItemCols` | `number` | `1` | Minimum column span during resize. |
213
+ | `minItemRows` | `number` | `1` | Minimum row span during resize. |
214
+
215
+ #### Outputs
216
+
217
+ | Output | Payload | Description |
218
+ |---|---|---|
219
+ | `itemChanged` | `Widget` | Fires after a single widget is moved or resized. |
220
+ | `layoutChanged` | `Widget[]` | Fires after any move or resize with the full updated list. |
165
221
 
166
222
  ---
167
223
 
224
+ ### Public Exports
225
+
226
+ ```typescript
227
+ export { DashboardModule } from '@ogidor/dashboard';
228
+ export { DashboardComponent } from '@ogidor/dashboard';
229
+ export { WidgetRendererComponent } from '@ogidor/dashboard';
230
+ export { CustomGridComponent } from '@ogidor/dashboard';
231
+ export { GridCellDirective } from '@ogidor/dashboard';
232
+ export { DashboardStateService } from '@ogidor/dashboard';
233
+ export { Widget, Page, DashboardConfig, DashboardTheme } from '@ogidor/dashboard';
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Models
239
+
168
240
  ### `Widget`
169
241
 
170
242
  ```typescript
171
243
  export interface Widget {
172
- id: string; // auto-generated — do not set manually
173
- x: number; // grid column (0-based)
174
- y: number; // grid row (0-based)
175
- cols: number; // column span (default: 4)
176
- rows: number; // row span (default: 3)
177
- title: string; // displayed in the card header
178
- cardColor?: string; // optional background color for this card
179
- data?: any; // your custom payload — stored & synced, never inspected
244
+ id: string; // auto-generated — do not set manually
245
+ x: number; // grid column, 0-based
246
+ y: number; // grid row, 0-based
247
+ cols: number; // column span (default: 4)
248
+ rows: number; // row span (default: 3)
249
+ title: string; // shown in the card header
250
+ cardColor?: string; // per-card background overrides the global widgetCardColor
251
+ data?: any; // custom payload — stored and synced, never inspected
180
252
  }
181
253
  ```
182
254
 
183
- ---
255
+ ### `Page`
184
256
 
185
- ### `DashboardTheme`
257
+ ```typescript
258
+ export interface Page {
259
+ id: string;
260
+ name: string;
261
+ widgets: Widget[];
262
+ }
263
+ ```
264
+
265
+ ### `DashboardConfig`
266
+
267
+ ```typescript
268
+ export interface DashboardConfig {
269
+ pages: Page[];
270
+ activePageId: string;
271
+ }
272
+ ```
186
273
 
187
- All fields are optional. Any omitted field falls back to its default.
274
+ ### `DashboardTheme`
188
275
 
189
276
  ```typescript
190
277
  export interface DashboardTheme {
191
- // ── Layout ──
192
- backgroundColor?: string; // wrapper background default: #000000
193
- panelColor?: string; // main panel background default: #1c1c1e
194
- widgetCardColor?: string; // card background (global) default: #2c2c2e
195
- foreColor?: string; // text / labels default: #8e8e93
196
- accentColor?: string; // buttons, active states default: #0a84ff
197
- dangerColor?: string; // remove / close actions default: #ff453a
198
- fontFamily?: string; // applied globally default: system-ui stack
199
-
200
- // ── Header / Tabs ──
201
- tabsContainerColor?: string; // tabs pill background default: rgba(44,44,46,0.6)
202
- tabActiveColor?: string; // active tab background default: #3a3a3c
203
- tabActiveTextColor?: string; // active tab text default: #ffffff
204
- tabHoverTextColor?: string; // hovered tab text default: #e5e5ea
205
- addWidgetButtonTextColor?: string; // "Add Widget" button text default: #ffffff
206
-
207
- // ── Pop-out window ──
208
- popoutTitleColor?: string; // pop-out window title text default: #ffffff
209
-
210
- // ── Widget card ──
211
- widgetTitleColor?: string; // card title text default: #ffffff
212
- dragHandleColor?: string; // drag-handle dot icon default: rgba(255,255,255,0.2)
213
- widgetBorderColor?: string; // card border default: rgba(255,255,255,0.07)
214
- widgetButtonBgColor?: string; // edit/remove button background default: rgba(255,255,255,0.07)
215
- widgetButtonColor?: string; // edit/remove button icon default: rgba(255,255,255,0.5)
216
-
217
- // ── Grid ──
218
- placeholderColor?: string; // drag/resize ghost overlay default: #0a84ff (accentColor)
219
- resizeHandleColor?: string; // resize handle icon default: rgba(255,255,255,0.25)
278
+ // Layout
279
+ backgroundColor?: string; // outer wrapper default: #000000
280
+ panelColor?: string; // main panel default: #1c1c1e
281
+ widgetCardColor?: string; // card background default: #2c2c2e
282
+ foreColor?: string; // text / labels default: #8e8e93
283
+ accentColor?: string; // active states, buttons default: #0a84ff
284
+ dangerColor?: string; // destructive actions default: #ff453a
285
+ fontFamily?: string; // global font stack default: system-ui
286
+
287
+ // Header & Tabs
288
+ tabsContainerColor?: string; // pill background default: rgba(44,44,46,0.6)
289
+ tabActiveColor?: string; // active tab bg default: #3a3a3c
290
+ tabActiveTextColor?: string; // active tab text default: #ffffff
291
+ tabHoverTextColor?: string; // hovered tab text default: #e5e5ea
292
+ addWidgetButtonTextColor?: string; // Add Widget label default: #ffffff
293
+
294
+ // Pop-out Window
295
+ popoutTitleColor?: string; // pop-out title text default: #ffffff
296
+
297
+ // Widget Card
298
+ widgetTitleColor?: string; // card title default: #ffffff
299
+ dragHandleColor?: string; // drag dots default: rgba(255,255,255,0.2)
300
+ widgetBorderColor?: string; // card border default: rgba(255,255,255,0.07)
301
+ widgetButtonBgColor?: string; // icon button bg default: rgba(255,255,255,0.07)
302
+ widgetButtonColor?: string; // icon button colour default: rgba(255,255,255,0.5)
303
+
304
+ // Grid
305
+ placeholderColor?: string; // drag ghost overlay default: #0a84ff
306
+ resizeHandleColor?: string; // resize icon default: rgba(255,255,255,0.25)
220
307
  }
221
308
  ```
222
309
 
223
- > **Tip:** you can also override individual widget card colors without touching the theme — set `cardColor` on any `Widget` object and it takes priority over `widgetCardColor`.
310
+ > **Per-card colour:** set `cardColor` on any `Widget` to override `widgetCardColor` for just that one card.
224
311
 
225
312
  ---
226
313
 
227
- ## CSS Custom Properties
314
+ ## Theming
315
+
316
+ ### Theme Input
228
317
 
229
- You can theme the dashboard entirely via CSS in your global stylesheet instead of (or alongside) the `theme` input. Every `DashboardTheme` property maps 1-to-1 to a CSS custom property:
318
+ Pass a partial `DashboardTheme` to `[theme]`. Only provided fields are overridden.
319
+
320
+ ```typescript
321
+ theme: DashboardTheme = {
322
+ accentColor: '#6c63ff',
323
+ tabActiveColor: '#2a2a38',
324
+ tabActiveTextColor: '#ffffff',
325
+ widgetTitleColor: '#f0f0f0',
326
+ placeholderColor: '#6c63ff',
327
+ };
328
+ ```
329
+
330
+ ```html
331
+ <app-dashboard [theme]="theme"></app-dashboard>
332
+ ```
333
+
334
+ ### CSS Custom Properties
335
+
336
+ Every token is also available as a CSS variable. Use them in your global stylesheet:
230
337
 
231
338
  ```css
232
339
  app-dashboard {
@@ -246,7 +353,7 @@ app-dashboard {
246
353
  --dash-tab-hover-text: #d0d0d5;
247
354
  --dash-add-widget-text: #ffffff;
248
355
 
249
- /* Pop-out window */
356
+ /* Pop-out */
250
357
  --dash-popout-title-color: #ffffff;
251
358
 
252
359
  /* Widget card */
@@ -262,9 +369,9 @@ app-dashboard {
262
369
  }
263
370
  ```
264
371
 
265
- ### CSS variable reference
372
+ ### CSS Variable Reference
266
373
 
267
- | CSS Variable | `DashboardTheme` property | Default |
374
+ | CSS Variable | `DashboardTheme` field | Default |
268
375
  |---|---|---|
269
376
  | `--dash-bg` | `backgroundColor` | `#000000` |
270
377
  | `--dash-panel-bg` | `panelColor` | `#1c1c1e` |
@@ -291,32 +398,24 @@ app-dashboard {
291
398
 
292
399
  ## Pop-out Windows
293
400
 
294
- Every workspace tab has a **pop-out** button (the arrow icon, visible on hover). Clicking it opens that workspace in a separate browser window.
401
+ Every workspace tab shows a pop-out button (arrow icon, visible on hover). Clicking it opens that workspace in a new browser window.
295
402
 
296
- The popped-out window:
297
- - shows only that one workspace — no tabs, no "Add Widget" bar, just the grid
298
- - has its **own independent card layout** — dragging and resizing cards there does not affect any other window
299
- - **does** receive structural changes from other windows in real time
403
+ The pop-out window:
300
404
 
301
- ### What syncs and what doesn't
405
+ - Shows only that workspace — no tabs, no Add Widget button, just the grid.
406
+ - Has its **own independent layout** — moving cards there never affects the main window.
407
+ - **Receives** structural changes (add/remove widget, metadata edits) from all other windows in real time.
302
408
 
303
- The library separates two kinds of state:
409
+ ### Sync behaviour
304
410
 
305
- | State | Synced across windows? | Stored where |
411
+ | State | Synced across windows? | Stored in |
306
412
  |---|---|---|
307
- | Page list (add / remove / rename workspace) | Yes | `ogidor_shared` in localStorage |
308
- | Widget list (add / remove a card) | Yes | `ogidor_shared` in localStorage |
309
- | Widget metadata (title, card color, data) | Yes | `ogidor_shared` in localStorage |
310
- | Card positions and sizes (drag / resize) | No — local only | `ogidor_positions_<windowId>` |
311
-
312
- **Example:** you have your main dashboard open in Tab A, and you pop out "Workspace 2" into Window B.
413
+ | Page list (add / remove / rename) | Yes | `ogidor_shared` |
414
+ | Widget list (add / remove) | Yes | `ogidor_shared` |
415
+ | Widget metadata (title, cardColor, data) | Yes | `ogidor_shared` |
416
+ | Card positions and sizes | No — per window | `ogidor_positions_<windowId>` |
313
417
 
314
- - You drag a card in **Window B** → it stays where you put it in B; Tab A is completely unaffected
315
- - You add a widget from **Tab A** → the new card appears in **Window B** at its default position
316
- - You delete a widget in **Window B** → it disappears from **Tab A** immediately
317
- - Each window remembers its own layout between page reloads
318
-
319
- To pop out a workspace programmatically:
418
+ To pop out programmatically:
320
419
 
321
420
  ```typescript
322
421
  this.state.popOutPage(pageId);
@@ -326,32 +425,39 @@ this.state.popOutPage(pageId);
326
425
 
327
426
  ## Persisting Layouts
328
427
 
329
- The library automatically persists state to `localStorage` on every change using two keys:
428
+ State is saved to `localStorage` automatically on every change.
330
429
 
331
- | Key | Contains |
430
+ | Key | Contents |
332
431
  |---|---|
333
- | `ogidor_shared` | Page list and widget metadata (titles, colors, data). Shared across all windows. |
334
- | `ogidor_positions_main` | Card positions/sizes for the main window. |
335
- | `ogidor_positions_<pageId>` | Card positions/sizes for each pop-out window, keyed by the workspace id. |
432
+ | `ogidor_shared` | Page list and widget metadata. Shared across all windows. |
433
+ | `ogidor_positions_main` | Grid positions for the main window. |
434
+ | `ogidor_positions_<pageId>` | Grid positions for each pop-out window. |
336
435
 
337
- To save the full current layout to a backend and restore it later:
436
+ To save and restore via a backend:
338
437
 
339
438
  ```typescript
340
- // Save (includes this window's positions)
439
+ // Save
341
440
  const json = this.dash.serializeLayout();
342
- await myApi.saveLayout(json);
441
+ await api.save(json);
343
442
 
344
443
  // Restore
345
- const json = await myApi.loadLayout();
444
+ const json = await api.load();
346
445
  this.dash.stateService.loadLayout(JSON.parse(json));
347
446
  ```
348
447
 
349
448
  ---
350
449
 
351
- ## Example: Full Integration
450
+ ## Full Integration Example
352
451
 
353
452
  ```typescript
354
- // app.component.ts
453
+ import { Component, ViewChild } from '@angular/core';
454
+ import {
455
+ DashboardComponent,
456
+ DashboardStateService,
457
+ DashboardTheme,
458
+ Widget,
459
+ } from '@ogidor/dashboard';
460
+
355
461
  @Component({
356
462
  selector: 'app-root',
357
463
  template: `
@@ -365,38 +471,38 @@ this.dash.stateService.loadLayout(JSON.parse(json));
365
471
  </ng-template>
366
472
  </app-dashboard>
367
473
 
368
- <!-- Your own add/edit dialogs -->
369
- <my-add-widget-dialog
474
+ <my-add-dialog
370
475
  *ngIf="addOpen"
371
476
  (confirmed)="onAddConfirmed($event)"
372
477
  (cancelled)="addOpen = false"
373
- ></my-add-widget-dialog>
478
+ ></my-add-dialog>
374
479
 
375
- <my-edit-widget-dialog
480
+ <my-edit-dialog
376
481
  *ngIf="editTarget"
377
482
  [widget]="editTarget"
378
483
  (confirmed)="onEditConfirmed($event)"
379
484
  (cancelled)="editTarget = null"
380
- ></my-edit-widget-dialog>
485
+ ></my-edit-dialog>
381
486
  `,
382
487
  })
383
488
  export class AppComponent {
489
+ @ViewChild(DashboardComponent) dash!: DashboardComponent;
490
+
384
491
  theme: DashboardTheme = {
385
- accentColor: '#6c63ff',
386
- placeholderColor: '#6c63ff',
387
- tabActiveColor: '#2a2a38',
388
- tabActiveTextColor: '#ffffff',
389
- widgetTitleColor: '#f0f0f0',
492
+ accentColor: '#6c63ff',
493
+ tabActiveColor: '#2a2a38',
494
+ widgetTitleColor: '#f0f0f0',
390
495
  };
496
+
391
497
  addOpen = false;
392
498
  editTarget: Widget | null = null;
393
499
 
394
500
  constructor(private state: DashboardStateService) {}
395
501
 
396
- openAddDialog() { this.addOpen = true; }
397
- openEditDialog(w: Widget) { this.editTarget = w; }
502
+ openAddDialog() { this.addOpen = true; }
503
+ openEditDialog(w: Widget) { this.editTarget = w; }
398
504
 
399
- onAddConfirmed(payload: { title: string; data: any; cols: number; rows: number }) {
505
+ onAddConfirmed(payload: Partial<Widget> & { title: string }) {
400
506
  this.state.addWidget(payload);
401
507
  this.addOpen = false;
402
508
  }
@@ -405,11 +511,30 @@ export class AppComponent {
405
511
  this.state.updateWidget(this.editTarget!.id, patch);
406
512
  this.editTarget = null;
407
513
  }
514
+
515
+ saveLayout() {
516
+ return this.dash.serializeLayout();
517
+ }
408
518
  }
409
519
  ```
410
520
 
411
521
  ---
412
522
 
523
+ ## Development
524
+
525
+ ```bash
526
+ # Install dependencies
527
+ npm install
528
+
529
+ # Build the library
530
+ npm run build:lib
531
+
532
+ # Run the local demo app
533
+ npm start
534
+ ```
535
+
536
+ ---
537
+
413
538
  ## License
414
539
 
415
540
  MIT