@dash-builder/angular 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @dash-builder/angular
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 37f3acf: First public release: a dashboard grid where widgets flow into a fixed number of columns and can be dragged and resized, with four drop modes (insert, block, shrink, swap), drag handles, locked widgets, a row limit and a narrow (stacked) view. `@dash-builder/core` works on its own; `@dash-builder/angular` and `@dash-builder/react` wrap it.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dash-builder contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,119 @@
1
- # Temporary Holding Version
1
+ # @dash-builder/angular
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Angular bindings for [`@dash-builder/core`](https://www.npmjs.com/package/@dash-builder/core), a dashboard grid where widgets flow left to right into a fixed number of columns and can be dragged and resized. The layout is an ordered list: array order is position, there are no x/y coordinates.
4
+
5
+ Requires Angular 22. Works with and without zone.js: the grid's DOM listeners run outside the Angular zone, and state flows through signals.
6
+
7
+ ## Install
8
+
9
+ `@dash-builder/core` is a peer dependency, so install both:
10
+
11
+ ```sh
12
+ pnpm add @dash-builder/angular @dash-builder/core
13
+ # or: npm install @dash-builder/angular @dash-builder/core
14
+ ```
15
+
16
+ ## Styles
17
+
18
+ Import the grid stylesheet once, in your global `styles.css`:
19
+
20
+ ```css
21
+ @import '@dash-builder/core/grid.css';
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ import { Component, signal } from '@angular/core';
28
+ import { DashGrid, DashHandle, DashWidget, type LayoutItem } from '@dash-builder/angular';
29
+
30
+ @Component({
31
+ selector: 'app-dashboard',
32
+ imports: [DashGrid, DashWidget, DashHandle],
33
+ template: `
34
+ <dash-grid [columns]="4" dropMode="insert" [(layout)]="layout" (layoutChange)="save($event)">
35
+ @for (item of layout(); track item.id) {
36
+ <div [dashWidget]="item.id">
37
+ <header dashHandle>{{ item.id }}</header>
38
+ <p>Widget content</p>
39
+ </div>
40
+ }
41
+ </dash-grid>
42
+ `,
43
+ })
44
+ export class Dashboard {
45
+ readonly layout = signal<LayoutItem[]>([
46
+ { id: 'a', w: 2, h: 2 },
47
+ { id: 'b', w: 1, h: 1 },
48
+ { id: 'c', w: 1, h: 1, locked: true },
49
+ ]);
50
+
51
+ save(layout: LayoutItem[]) {
52
+ localStorage.setItem('dashboard-layout', JSON.stringify(layout));
53
+ }
54
+ }
55
+ ```
56
+
57
+ - `<dash-grid>` is the grid container. Each projected element with `[dashWidget]="id"` is a widget; the id must match a layout entry. Widgets must be direct children of `<dash-grid>` (an `@for` block directly inside it is fine). Widgets rendered through `ngTemplateOutlet`, or inside a wrapper component's own template, are not direct children (Angular content projection only sees what the template using `<dash-grid>` places directly in it) and are not picked up.
58
+ - `dashHandle` marks the only element a widget can be dragged by. Without one, the whole widget is the drag surface.
59
+ - Render widgets from the layout with `@for (item of layout(); track item.id)`. Widgets that are added, removed, reordered or given a new id are picked up after Angular updates the DOM. Don't use `track item` (object identity): the grid emits copies of the items, so every widget would be destroyed and recreated after each drop.
60
+
61
+ ## API
62
+
63
+ ### `DashGrid` (`<dash-grid>`)
64
+
65
+ | Input | Type | Default | Description |
66
+ | --- | --- | --- | --- |
67
+ | `layout` | `LayoutItem[]` | required | Two-way: `[(layout)]` or `[layout]` + `(layoutChange)`. |
68
+ | `columns` | `number` | required | Number of columns. |
69
+ | `dropMode` | `'insert' \| 'block' \| 'shrink' \| 'swap'` | `'insert'` | Changeable at runtime. |
70
+ | `maxRows` | `number \| undefined` | none | Hard row limit for drops, resizes and `addItem`. |
71
+ | `rowHeight` | `number` | `80` | Row height in px. |
72
+ | `gap` | `number` | `8` | Gap between cells in px. |
73
+ | `breakpoint` | `number` | `640` | Container width in px below which the narrow view is used. |
74
+ | `ghost` | `(el: HTMLElement) => HTMLElement` | sanitized clone | Builds the drag ghost (see the core README). |
75
+
76
+ The numeric inputs (`columns`, `maxRows`, `rowHeight`, `gap`, `breakpoint`) also accept static attributes, e.g. `columns="4"`.
77
+
78
+ | Output | Payload | When |
79
+ | --- | --- | --- |
80
+ | `layoutChange` | `LayoutItem[]` | A drop or resize is released, `addItem` / `removeItem` succeed, or a `columns` change normalizes widths. Setting `layout` from outside never emits. |
81
+ | `reject` | `RejectReason` | An `addItem` is refused (`'max-rows'`, `'duplicate'` or `'invalid'`). |
82
+
83
+ | Method | Description |
84
+ | --- | --- |
85
+ | `addItem(item: LayoutItem, index?: number): DropResult` | Inserts at `index` (default: append). On success updates `layout` and emits `layoutChange`; otherwise emits `reject` and returns `{ ok: false, reason }`. |
86
+ | `removeItem(id: string): void` | Removes the item and emits `layoutChange`; an unknown id does nothing. |
87
+
88
+ Get the instance with `viewChild.required(DashGrid)`.
89
+
90
+ ### `DashWidget` (`[dashWidget]`) and `DashHandle` (`[dashHandle]`)
91
+
92
+ `dashWidget` takes the widget id (required) and sets `data-grid-id`; `dashHandle` sets `data-grid-handle`.
93
+
94
+ ### Adding widgets: use `addItem`
95
+
96
+ Prefer `grid.addItem(item)` over pushing to the layout yourself:
97
+
98
+ ```ts
99
+ readonly grid = viewChild.required(DashGrid);
100
+
101
+ add() {
102
+ const result = this.grid().addItem({ id: crypto.randomUUID(), w: 1, h: 1 });
103
+ if (!result.ok) console.warn('not added:', result.reason);
104
+ }
105
+ ```
106
+
107
+ `addItem` enforces `maxRows`, refuses duplicate ids and reports the refusal through `reject`. A plain `layout.update((l) => [...l, item])` bypasses all of that: the grid shows whatever layout it is given.
108
+
109
+ ## Narrow view
110
+
111
+ When `<dash-grid>` is narrower than `breakpoint` (default 640 px), widgets stack in a single column in list order and resizing is disabled. Dragging still reorders them, but only temporarily: a narrow-view reorder never emits `layoutChange`, so the saved layout is untouched. Set `[breakpoint]="0"` to turn the narrow view off.
112
+
113
+ ## Drop modes
114
+
115
+ `insert`, `block`, `shrink` and `swap` are described in the [core README](https://www.npmjs.com/package/@dash-builder/core#drop-modes), together with locked widgets, `maxRows` and the layout JSON format.
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,367 @@
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef, NgZone, afterNextRender, afterRenderEffect, computed, contentChildren, effect, inject, input, model, numberAttribute, output, untracked } from "@angular/core";
3
+ import { addItem, createGrid, normalizeLayout, removeItem } from "@dash-builder/core";
4
+ var DashWidget = class DashWidget {
5
+ dashWidget = input.required(...ngDevMode ? [{ debugName: "dashWidget" }] : /* istanbul ignore next */ []);
6
+ static ɵfac = i0.ɵɵngDeclareFactory({
7
+ minVersion: "12.0.0",
8
+ version: "22.2.3",
9
+ ngImport: i0,
10
+ type: DashWidget,
11
+ deps: [],
12
+ target: i0.ɵɵFactoryTarget.Directive
13
+ });
14
+ static ɵdir = i0.ɵɵngDeclareDirective({
15
+ minVersion: "17.1.0",
16
+ version: "22.2.3",
17
+ type: DashWidget,
18
+ isStandalone: true,
19
+ selector: "[dashWidget]",
20
+ inputs: { dashWidget: {
21
+ classPropertyName: "dashWidget",
22
+ publicName: "dashWidget",
23
+ isSignal: true,
24
+ isRequired: true,
25
+ transformFunction: null
26
+ } },
27
+ host: { properties: { "attr.data-grid-id": "dashWidget()" } },
28
+ ngImport: i0
29
+ });
30
+ };
31
+ i0.ɵɵngDeclareClassMetadata({
32
+ minVersion: "12.0.0",
33
+ version: "22.2.3",
34
+ ngImport: i0,
35
+ type: DashWidget,
36
+ decorators: [{
37
+ type: Directive,
38
+ args: [{
39
+ selector: "[dashWidget]",
40
+ host: { "[attr.data-grid-id]": "dashWidget()" }
41
+ }]
42
+ }],
43
+ propDecorators: { dashWidget: [{
44
+ type: i0.Input,
45
+ args: [{
46
+ isSignal: true,
47
+ alias: "dashWidget",
48
+ required: true
49
+ }]
50
+ }] }
51
+ });
52
+ var DashHandle = class DashHandle {
53
+ static ɵfac = i0.ɵɵngDeclareFactory({
54
+ minVersion: "12.0.0",
55
+ version: "22.2.3",
56
+ ngImport: i0,
57
+ type: DashHandle,
58
+ deps: [],
59
+ target: i0.ɵɵFactoryTarget.Directive
60
+ });
61
+ static ɵdir = i0.ɵɵngDeclareDirective({
62
+ minVersion: "14.0.0",
63
+ version: "22.2.3",
64
+ type: DashHandle,
65
+ isStandalone: true,
66
+ selector: "[dashHandle]",
67
+ host: { attributes: { "data-grid-handle": "" } },
68
+ ngImport: i0
69
+ });
70
+ };
71
+ i0.ɵɵngDeclareClassMetadata({
72
+ minVersion: "12.0.0",
73
+ version: "22.2.3",
74
+ ngImport: i0,
75
+ type: DashHandle,
76
+ decorators: [{
77
+ type: Directive,
78
+ args: [{
79
+ selector: "[dashHandle]",
80
+ host: { "data-grid-handle": "" }
81
+ }]
82
+ }]
83
+ });
84
+ function optionalNumber(value) {
85
+ return value == null || value === "" ? void 0 : numberAttribute(value);
86
+ }
87
+ var DashGrid = class DashGrid {
88
+ layout = model.required(...ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []);
89
+ columns = input.required({
90
+ ...ngDevMode ? { debugName: "columns" } : /* istanbul ignore next */ {},
91
+ transform: numberAttribute
92
+ });
93
+ dropMode = input("insert", ...ngDevMode ? [{ debugName: "dropMode" }] : /* istanbul ignore next */ []);
94
+ maxRows = input(void 0, {
95
+ ...ngDevMode ? { debugName: "maxRows" } : /* istanbul ignore next */ {},
96
+ transform: optionalNumber
97
+ });
98
+ rowHeight = input(80, {
99
+ ...ngDevMode ? { debugName: "rowHeight" } : /* istanbul ignore next */ {},
100
+ transform: numberAttribute
101
+ });
102
+ gap = input(8, {
103
+ ...ngDevMode ? { debugName: "gap" } : /* istanbul ignore next */ {},
104
+ transform: numberAttribute
105
+ });
106
+ breakpoint = input(640, {
107
+ ...ngDevMode ? { debugName: "breakpoint" } : /* istanbul ignore next */ {},
108
+ transform: numberAttribute
109
+ });
110
+ ghost = input(...ngDevMode ? [void 0, { debugName: "ghost" }] : /* istanbul ignore next */ []);
111
+ reject = output();
112
+ zone = inject(NgZone);
113
+ host = inject(ElementRef).nativeElement;
114
+ widgets = contentChildren(DashWidget, ...ngDevMode ? [{ debugName: "widgets" }] : /* istanbul ignore next */ []);
115
+ grid;
116
+ applied;
117
+ superseded = false;
118
+ options = computed(() => ({
119
+ columns: this.columns(),
120
+ dropMode: this.dropMode(),
121
+ maxRows: this.maxRows(),
122
+ rowHeight: this.rowHeight(),
123
+ gap: this.gap(),
124
+ breakpoint: this.breakpoint(),
125
+ ghost: this.ghost()
126
+ }), ...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
127
+ constructor() {
128
+ afterNextRender(() => {
129
+ const layout = this.applied = untracked(this.layout);
130
+ this.grid = this.zone.runOutsideAngular(() => createGrid(this.host, {
131
+ ...untracked(this.options),
132
+ layout,
133
+ onLayoutChange: (l) => {
134
+ if (!this.superseded) this.zone.run(() => this.layout.set(l));
135
+ },
136
+ onReject: (r) => this.zone.run(() => this.reject.emit(r))
137
+ }));
138
+ });
139
+ inject(DestroyRef).onDestroy(() => {
140
+ this.grid?.destroy();
141
+ this.grid = void 0;
142
+ });
143
+ effect(() => {
144
+ const opts = this.options();
145
+ const layout = this.layout();
146
+ this.outside((g) => {
147
+ if (layout === this.applied) {
148
+ g.setOptions(opts);
149
+ return;
150
+ }
151
+ this.applied = layout;
152
+ this.superseded = true;
153
+ try {
154
+ g.setOptions(opts);
155
+ } finally {
156
+ this.superseded = false;
157
+ }
158
+ g.setLayout(layout);
159
+ });
160
+ });
161
+ afterRenderEffect(() => {
162
+ this.widgets().forEach((w) => w.dashWidget());
163
+ this.outside((g) => g.refresh());
164
+ });
165
+ }
166
+ addItem(item, index) {
167
+ const config = {
168
+ columns: this.columns(),
169
+ maxRows: this.maxRows(),
170
+ mode: this.dropMode()
171
+ };
172
+ const result = addItem(normalizeLayout(this.layout(), config.columns), config, item, index);
173
+ if (result.ok) this.layout.set(result.layout);
174
+ else this.reject.emit(result.reason);
175
+ return result;
176
+ }
177
+ removeItem(id) {
178
+ const current = this.layout();
179
+ const next = removeItem(current, id);
180
+ if (next !== current) this.layout.set(next);
181
+ }
182
+ outside(fn) {
183
+ const grid = this.grid;
184
+ if (grid) this.zone.runOutsideAngular(() => fn(grid));
185
+ }
186
+ static ɵfac = i0.ɵɵngDeclareFactory({
187
+ minVersion: "12.0.0",
188
+ version: "22.2.3",
189
+ ngImport: i0,
190
+ type: DashGrid,
191
+ deps: [],
192
+ target: i0.ɵɵFactoryTarget.Component
193
+ });
194
+ static ɵcmp = i0.ɵɵngDeclareComponent({
195
+ minVersion: "17.2.0",
196
+ version: "22.2.3",
197
+ type: DashGrid,
198
+ isStandalone: true,
199
+ selector: "dash-grid",
200
+ inputs: {
201
+ layout: {
202
+ classPropertyName: "layout",
203
+ publicName: "layout",
204
+ isSignal: true,
205
+ isRequired: true,
206
+ transformFunction: null
207
+ },
208
+ columns: {
209
+ classPropertyName: "columns",
210
+ publicName: "columns",
211
+ isSignal: true,
212
+ isRequired: true,
213
+ transformFunction: null
214
+ },
215
+ dropMode: {
216
+ classPropertyName: "dropMode",
217
+ publicName: "dropMode",
218
+ isSignal: true,
219
+ isRequired: false,
220
+ transformFunction: null
221
+ },
222
+ maxRows: {
223
+ classPropertyName: "maxRows",
224
+ publicName: "maxRows",
225
+ isSignal: true,
226
+ isRequired: false,
227
+ transformFunction: null
228
+ },
229
+ rowHeight: {
230
+ classPropertyName: "rowHeight",
231
+ publicName: "rowHeight",
232
+ isSignal: true,
233
+ isRequired: false,
234
+ transformFunction: null
235
+ },
236
+ gap: {
237
+ classPropertyName: "gap",
238
+ publicName: "gap",
239
+ isSignal: true,
240
+ isRequired: false,
241
+ transformFunction: null
242
+ },
243
+ breakpoint: {
244
+ classPropertyName: "breakpoint",
245
+ publicName: "breakpoint",
246
+ isSignal: true,
247
+ isRequired: false,
248
+ transformFunction: null
249
+ },
250
+ ghost: {
251
+ classPropertyName: "ghost",
252
+ publicName: "ghost",
253
+ isSignal: true,
254
+ isRequired: false,
255
+ transformFunction: null
256
+ }
257
+ },
258
+ outputs: {
259
+ layout: "layoutChange",
260
+ reject: "reject"
261
+ },
262
+ queries: [{
263
+ propertyName: "widgets",
264
+ predicate: DashWidget,
265
+ isSignal: true
266
+ }],
267
+ ngImport: i0,
268
+ template: "<ng-content />",
269
+ isInline: true,
270
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
271
+ });
272
+ };
273
+ i0.ɵɵngDeclareClassMetadata({
274
+ minVersion: "12.0.0",
275
+ version: "22.2.3",
276
+ ngImport: i0,
277
+ type: DashGrid,
278
+ decorators: [{
279
+ type: Component,
280
+ args: [{
281
+ selector: "dash-grid",
282
+ template: "<ng-content />",
283
+ changeDetection: ChangeDetectionStrategy.OnPush
284
+ }]
285
+ }],
286
+ ctorParameters: () => [],
287
+ propDecorators: {
288
+ layout: [{
289
+ type: i0.Input,
290
+ args: [{
291
+ isSignal: true,
292
+ alias: "layout",
293
+ required: true
294
+ }]
295
+ }, {
296
+ type: i0.Output,
297
+ args: ["layoutChange"]
298
+ }],
299
+ columns: [{
300
+ type: i0.Input,
301
+ args: [{
302
+ isSignal: true,
303
+ alias: "columns",
304
+ required: true
305
+ }]
306
+ }],
307
+ dropMode: [{
308
+ type: i0.Input,
309
+ args: [{
310
+ isSignal: true,
311
+ alias: "dropMode",
312
+ required: false
313
+ }]
314
+ }],
315
+ maxRows: [{
316
+ type: i0.Input,
317
+ args: [{
318
+ isSignal: true,
319
+ alias: "maxRows",
320
+ required: false
321
+ }]
322
+ }],
323
+ rowHeight: [{
324
+ type: i0.Input,
325
+ args: [{
326
+ isSignal: true,
327
+ alias: "rowHeight",
328
+ required: false
329
+ }]
330
+ }],
331
+ gap: [{
332
+ type: i0.Input,
333
+ args: [{
334
+ isSignal: true,
335
+ alias: "gap",
336
+ required: false
337
+ }]
338
+ }],
339
+ breakpoint: [{
340
+ type: i0.Input,
341
+ args: [{
342
+ isSignal: true,
343
+ alias: "breakpoint",
344
+ required: false
345
+ }]
346
+ }],
347
+ ghost: [{
348
+ type: i0.Input,
349
+ args: [{
350
+ isSignal: true,
351
+ alias: "ghost",
352
+ required: false
353
+ }]
354
+ }],
355
+ reject: [{
356
+ type: i0.Output,
357
+ args: ["reject"]
358
+ }],
359
+ widgets: [{
360
+ type: i0.ContentChildren,
361
+ args: [i0.forwardRef(() => DashWidget), { isSignal: true }]
362
+ }]
363
+ }
364
+ });
365
+ export { DashGrid, DashHandle, DashWidget };
366
+
367
+ //# sourceMappingURL=dash-builder-angular.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dash-builder-angular.mjs","names":["planAdd","planRemove"],"sources":["../../../projects/grid-angular/src/lib/widget.directive.ts","../../../projects/grid-angular/src/lib/handle.directive.ts","../../../projects/grid-angular/src/lib/grid.component.ts"],"sourcesContent":["import { Directive, input } from '@angular/core';\n\n/** Marks an element as a grid widget; the id is read by the core via `data-grid-id`. */\n@Directive({\n selector: '[dashWidget]',\n host: { '[attr.data-grid-id]': 'dashWidget()' },\n})\nexport class DashWidget {\n readonly dashWidget = input.required<string>();\n}\n","import { Directive } from '@angular/core';\n\n/** Restricts drag start to this element within a widget (`data-grid-handle`). */\n@Directive({\n selector: '[dashHandle]',\n host: { 'data-grid-handle': '' },\n})\nexport class DashHandle {}\n","import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n afterNextRender,\n afterRenderEffect,\n computed,\n contentChildren,\n effect,\n inject,\n input,\n model,\n numberAttribute,\n output,\n untracked,\n} from '@angular/core';\nimport { addItem as planAdd, createGrid, normalizeLayout, removeItem as planRemove } from '@dash-builder/core';\nimport type { DropMode, DropResult, GridController, GridOptions, LayoutItem, RejectReason } from '@dash-builder/core';\nimport { DashWidget } from './widget.directive';\n\n/** `numberAttribute`, but unset (`undefined`, `null` or an empty attribute) stays `undefined`. */\nfunction optionalNumber(value: unknown): number | undefined {\n return value == null || value === '' ? undefined : numberAttribute(value);\n}\n\n/**\n * A dashboard grid around the `@dash-builder/core` controller. The host element is\n * the grid container; projected `[dashWidget]` elements are its widgets.\n */\n@Component({\n selector: 'dash-grid',\n template: '<ng-content />',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DashGrid {\n readonly layout = model.required<LayoutItem[]>();\n readonly columns = input.required<number, unknown>({ transform: numberAttribute });\n readonly dropMode = input<DropMode>('insert');\n readonly maxRows = input<number | undefined, unknown>(undefined, { transform: optionalNumber });\n readonly rowHeight = input<number, unknown>(80, { transform: numberAttribute });\n readonly gap = input<number, unknown>(8, { transform: numberAttribute });\n readonly breakpoint = input<number, unknown>(640, { transform: numberAttribute });\n readonly ghost = input<GridOptions['ghost']>();\n\n readonly reject = output<RejectReason>();\n\n private readonly zone = inject(NgZone);\n private readonly host: HTMLElement = inject(ElementRef).nativeElement;\n private readonly widgets = contentChildren(DashWidget);\n private grid?: GridController;\n /** The layout last handed to the controller. */\n private applied?: LayoutItem[];\n /** Set while setOptions runs before a new layout replaces the current one. */\n private superseded = false;\n\n private readonly options = computed(() => ({\n columns: this.columns(),\n dropMode: this.dropMode(),\n maxRows: this.maxRows(),\n rowHeight: this.rowHeight(),\n gap: this.gap(),\n breakpoint: this.breakpoint(),\n ghost: this.ghost(),\n }));\n\n constructor() {\n afterNextRender(() => {\n const layout = (this.applied = untracked(this.layout));\n this.grid = this.zone.runOutsideAngular(() =>\n createGrid(this.host, {\n ...untracked(this.options),\n layout,\n onLayoutChange: (l) => {\n if (!this.superseded) this.zone.run(() => this.layout.set(l));\n },\n onReject: (r) => this.zone.run(() => this.reject.emit(r)),\n }),\n );\n });\n inject(DestroyRef).onDestroy(() => {\n this.grid?.destroy();\n this.grid = undefined;\n });\n\n // Options and layout in one effect, so a columns change and a new layout from the\n // same tick are applied together. Every controller call binds or unbinds DOM\n // listeners, so all of them run outside the zone.\n effect(() => {\n const opts = this.options();\n const layout = this.layout();\n this.outside((g) => {\n if (layout === this.applied) {\n g.setOptions(opts); // a columns change may normalize the layout and emit\n return;\n }\n this.applied = layout;\n // The layout re-normalized for new columns is about to be replaced: don't emit it.\n this.superseded = true;\n try {\n g.setOptions(opts);\n } finally {\n this.superseded = false;\n }\n g.setLayout(layout); // normalized against the new columns; no-op on our own echo\n });\n });\n // After the DOM update: bind added, re-id'd or reordered widgets, release removed ones.\n afterRenderEffect(() => {\n this.widgets().forEach((w) => w.dashWidget()); // tracks every widget id\n this.outside((g) => g.refresh());\n });\n }\n\n /**\n * Adds `item` at `index` (default: append), enforcing `maxRows`. On success the\n * layout is updated and `layoutChange` emits; otherwise `reject` emits the reason.\n */\n addItem(item: LayoutItem, index?: number): DropResult {\n const config = { columns: this.columns(), maxRows: this.maxRows(), mode: this.dropMode() };\n // Plan on what the core renders (normalized), so maxRows holds for e.g. minH > h.\n const result = planAdd(normalizeLayout(this.layout(), config.columns), config, item, index);\n if (result.ok) this.layout.set(result.layout);\n else this.reject.emit(result.reason);\n return result;\n }\n\n /** Removes the item with `id`; an unknown id changes nothing and emits nothing. */\n removeItem(id: string): void {\n const current = this.layout();\n const next = planRemove(current, id);\n if (next !== current) this.layout.set(next);\n }\n\n private outside(fn: (grid: GridController) => void): void {\n const grid = this.grid;\n if (grid) this.zone.runOutsideAngular(() => fn(grid));\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@dash-builder/angular",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Angular bindings for @dash-builder/core: a dashboard grid with draggable, resizable widgets in a fixed number of columns.",
5
+ "license": "MIT",
6
+ "author": "Konstantin Penzin",
7
+ "homepage": "https://constantine-24.github.io/dash-builder/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Constantine-24/dash-builder.git",
11
+ "directory": "packages/angular/projects/grid-angular"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Constantine-24/dash-builder/issues"
15
+ },
16
+ "keywords": [
17
+ "dashboard",
18
+ "grid",
19
+ "layout",
20
+ "widgets",
21
+ "drag-and-drop",
22
+ "resizable",
23
+ "draggable",
24
+ "angular"
25
+ ],
26
+ "peerDependencies": {
27
+ "@angular/common": "^22.0.0",
28
+ "@angular/core": "^22.0.0",
29
+ "@dash-builder/core": "0.x"
30
+ },
31
+ "sideEffects": false,
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "directory": "../../dist/grid-angular",
35
+ "linkDirectory": false
36
+ },
37
+ "module": "fesm2022/dash-builder-angular.mjs",
38
+ "typings": "types/dash-builder-angular.d.ts",
39
+ "exports": {
40
+ "./package.json": {
41
+ "default": "./package.json"
42
+ },
43
+ ".": {
44
+ "types": "./types/dash-builder-angular.d.ts",
45
+ "default": "./fesm2022/dash-builder-angular.mjs"
46
+ }
47
+ },
48
+ "type": "module",
49
+ "dependencies": {
50
+ "tslib": "^2.3.0"
51
+ }
6
52
  }
@@ -0,0 +1,99 @@
1
+ import * as i0 from "@angular/core";
2
+ import { DropMode, DropMode as DropMode$1, DropResult, DropResult as DropResult$1, LayoutItem, LayoutItem as LayoutItem$1, RejectReason, RejectReason as RejectReason$1 } from "@dash-builder/core";
3
+ /** Marks an element as a grid widget; the id is read by the core via `data-grid-id`. */
4
+ export declare class DashWidget {
5
+ readonly dashWidget: import("@angular/core").InputSignal<string>;
6
+ static ɵfac: i0.ɵɵFactoryDeclaration<DashWidget, never>;
7
+ static ɵdir: i0.ɵɵDirectiveDeclaration<DashWidget, "[dashWidget]", never, {
8
+ "dashWidget": {
9
+ "alias": "dashWidget";
10
+ "required": true;
11
+ "isSignal": true;
12
+ };
13
+ }, {}, never, never, true, never>;
14
+ }
15
+ /** Restricts drag start to this element within a widget (`data-grid-handle`). */
16
+ export declare class DashHandle {
17
+ static ɵfac: i0.ɵɵFactoryDeclaration<DashHandle, never>;
18
+ static ɵdir: i0.ɵɵDirectiveDeclaration<DashHandle, "[dashHandle]", never, {}, {}, never, never, true, never>;
19
+ }
20
+ /**
21
+ * A dashboard grid around the `@dash-builder/core` controller. The host element is
22
+ * the grid container; projected `[dashWidget]` elements are its widgets.
23
+ */
24
+ export declare class DashGrid {
25
+ readonly layout: import("@angular/core").ModelSignal<LayoutItem$1[]>;
26
+ readonly columns: import("@angular/core").InputSignalWithTransform<number, unknown>;
27
+ readonly dropMode: import("@angular/core").InputSignal<DropMode$1>;
28
+ readonly maxRows: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
29
+ readonly rowHeight: import("@angular/core").InputSignalWithTransform<number, unknown>;
30
+ readonly gap: import("@angular/core").InputSignalWithTransform<number, unknown>;
31
+ readonly breakpoint: import("@angular/core").InputSignalWithTransform<number, unknown>;
32
+ readonly ghost: import("@angular/core").InputSignal<((el: HTMLElement) => HTMLElement) | undefined>;
33
+ readonly reject: import("@angular/core").OutputEmitterRef<RejectReason$1>;
34
+ private readonly zone;
35
+ private readonly host;
36
+ private readonly widgets;
37
+ private grid?;
38
+ /** The layout last handed to the controller. */
39
+ private applied?;
40
+ /** Set while setOptions runs before a new layout replaces the current one. */
41
+ private superseded;
42
+ private readonly options;
43
+ constructor();
44
+ /**
45
+ * Adds `item` at `index` (default: append), enforcing `maxRows`. On success the
46
+ * layout is updated and `layoutChange` emits; otherwise `reject` emits the reason.
47
+ */
48
+ addItem(item: LayoutItem$1, index?: number): DropResult$1;
49
+ /** Removes the item with `id`; an unknown id changes nothing and emits nothing. */
50
+ removeItem(id: string): void;
51
+ private outside;
52
+ static ɵfac: i0.ɵɵFactoryDeclaration<DashGrid, never>;
53
+ static ɵcmp: i0.ɵɵComponentDeclaration<DashGrid, "dash-grid", never, {
54
+ "layout": {
55
+ "alias": "layout";
56
+ "required": true;
57
+ "isSignal": true;
58
+ };
59
+ "columns": {
60
+ "alias": "columns";
61
+ "required": true;
62
+ "isSignal": true;
63
+ };
64
+ "dropMode": {
65
+ "alias": "dropMode";
66
+ "required": false;
67
+ "isSignal": true;
68
+ };
69
+ "maxRows": {
70
+ "alias": "maxRows";
71
+ "required": false;
72
+ "isSignal": true;
73
+ };
74
+ "rowHeight": {
75
+ "alias": "rowHeight";
76
+ "required": false;
77
+ "isSignal": true;
78
+ };
79
+ "gap": {
80
+ "alias": "gap";
81
+ "required": false;
82
+ "isSignal": true;
83
+ };
84
+ "breakpoint": {
85
+ "alias": "breakpoint";
86
+ "required": false;
87
+ "isSignal": true;
88
+ };
89
+ "ghost": {
90
+ "alias": "ghost";
91
+ "required": false;
92
+ "isSignal": true;
93
+ };
94
+ }, {
95
+ "layout": "layoutChange";
96
+ "reject": "reject";
97
+ }, ["widgets"], ["*"], true, never>;
98
+ }
99
+ export type { DropMode, DropResult, LayoutItem, RejectReason };