gridstack 8.1.1 → 8.2.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.
Files changed (158) hide show
  1. package/README.md +12 -11
  2. package/angular/.editorconfig +16 -0
  3. package/angular/.vscode/extensions.json +4 -0
  4. package/angular/.vscode/launch.json +20 -0
  5. package/angular/.vscode/tasks.json +42 -0
  6. package/{dist/ng → angular}/README.md +170 -154
  7. package/angular/README_build.md +27 -0
  8. package/angular/angular.json +132 -0
  9. package/angular/package.json +40 -0
  10. package/angular/projects/demo/.browserslistrc +16 -0
  11. package/angular/projects/demo/src/app/app.component.css +11 -0
  12. package/angular/projects/demo/src/app/app.component.html +78 -0
  13. package/angular/projects/demo/src/app/app.component.spec.ts +25 -0
  14. package/angular/projects/demo/src/app/app.component.ts +208 -0
  15. package/angular/projects/demo/src/app/app.module.ts +39 -0
  16. package/angular/projects/demo/src/app/dummy.component.ts +35 -0
  17. package/angular/projects/demo/src/app/ngFor.ts +131 -0
  18. package/angular/projects/demo/src/app/ngFor_cmd.ts +106 -0
  19. package/angular/projects/demo/src/app/simple.ts +46 -0
  20. package/angular/projects/demo/src/assets/.gitkeep +0 -0
  21. package/angular/projects/demo/src/environments/environment.prod.ts +3 -0
  22. package/angular/projects/demo/src/environments/environment.ts +16 -0
  23. package/angular/projects/demo/src/favicon.ico +0 -0
  24. package/angular/projects/demo/src/index.html +13 -0
  25. package/angular/projects/demo/src/main.ts +12 -0
  26. package/angular/projects/demo/src/polyfills.ts +53 -0
  27. package/angular/projects/demo/src/styles.css +4 -0
  28. package/angular/projects/demo/src/test.ts +26 -0
  29. package/angular/projects/demo/tsconfig.app.json +15 -0
  30. package/angular/projects/demo/tsconfig.spec.json +18 -0
  31. package/angular/projects/lib/README.md +24 -0
  32. package/angular/projects/lib/ng-package.json +7 -0
  33. package/angular/projects/lib/package.json +11 -0
  34. package/angular/projects/lib/src/lib/base-widget.ts +28 -0
  35. package/angular/projects/lib/src/lib/gridstack-item.component.ts +78 -0
  36. package/angular/projects/lib/src/lib/gridstack.component.ts +287 -0
  37. package/angular/projects/lib/src/lib/gridstack.module.ts +32 -0
  38. package/angular/projects/lib/src/public-api.ts +8 -0
  39. package/angular/projects/lib/src/test.ts +27 -0
  40. package/angular/projects/lib/tsconfig.lib.json +15 -0
  41. package/angular/projects/lib/tsconfig.lib.prod.json +10 -0
  42. package/angular/projects/lib/tsconfig.spec.json +17 -0
  43. package/dist/angular/README.md +170 -0
  44. package/dist/angular/esm2020/gridstack-angular.mjs +5 -0
  45. package/dist/angular/esm2020/lib/base-widget.mjs +30 -0
  46. package/dist/angular/esm2020/lib/gridstack-item.component.mjs +68 -0
  47. package/dist/angular/esm2020/lib/gridstack.component.mjs +278 -0
  48. package/dist/angular/esm2020/lib/gridstack.module.mjs +39 -0
  49. package/dist/angular/esm2020/public-api.mjs +8 -0
  50. package/dist/angular/fesm2015/gridstack-angular.mjs +418 -0
  51. package/dist/angular/fesm2015/gridstack-angular.mjs.map +1 -0
  52. package/dist/angular/fesm2020/gridstack-angular.mjs +413 -0
  53. package/dist/angular/fesm2020/gridstack-angular.mjs.map +1 -0
  54. package/dist/angular/index.d.ts +5 -0
  55. package/dist/angular/lib/base-widget.d.ts +16 -0
  56. package/dist/{ng → angular/lib}/gridstack-item.component.d.ts +37 -29
  57. package/dist/{ng → angular/lib}/gridstack.component.d.ts +129 -118
  58. package/dist/angular/lib/gridstack.module.d.ts +10 -0
  59. package/dist/angular/package.json +31 -0
  60. package/dist/angular/public-api.d.ts +4 -0
  61. package/dist/angular/src/base-widget.ts +28 -0
  62. package/dist/angular/src/gridstack-item.component.ts +78 -0
  63. package/dist/angular/src/gridstack.component.ts +287 -0
  64. package/dist/angular/src/gridstack.module.ts +32 -0
  65. package/dist/dd-base-impl.d.ts +1 -1
  66. package/dist/dd-base-impl.js +1 -1
  67. package/dist/dd-base-impl.js.map +1 -1
  68. package/dist/dd-draggable.d.ts +1 -1
  69. package/dist/dd-draggable.js +1 -1
  70. package/dist/dd-draggable.js.map +1 -1
  71. package/dist/dd-droppable.d.ts +1 -1
  72. package/dist/dd-droppable.js +1 -1
  73. package/dist/dd-droppable.js.map +1 -1
  74. package/dist/dd-element.d.ts +1 -1
  75. package/dist/dd-element.js +1 -1
  76. package/dist/dd-element.js.map +1 -1
  77. package/dist/dd-gridstack.d.ts +1 -1
  78. package/dist/dd-gridstack.js +1 -1
  79. package/dist/dd-gridstack.js.map +1 -1
  80. package/dist/dd-manager.d.ts +1 -1
  81. package/dist/dd-manager.js +1 -1
  82. package/dist/dd-manager.js.map +1 -1
  83. package/dist/dd-resizable-handle.d.ts +1 -1
  84. package/dist/dd-resizable-handle.js +1 -1
  85. package/dist/dd-resizable-handle.js.map +1 -1
  86. package/dist/dd-resizable.d.ts +1 -1
  87. package/dist/dd-resizable.js +1 -1
  88. package/dist/dd-resizable.js.map +1 -1
  89. package/dist/dd-touch.d.ts +1 -1
  90. package/dist/dd-touch.js +1 -1
  91. package/dist/dd-touch.js.map +1 -1
  92. package/dist/es5/dd-base-impl.d.ts +1 -1
  93. package/dist/es5/dd-base-impl.js +1 -1
  94. package/dist/es5/dd-base-impl.js.map +1 -1
  95. package/dist/es5/dd-draggable.d.ts +1 -1
  96. package/dist/es5/dd-draggable.js +1 -1
  97. package/dist/es5/dd-draggable.js.map +1 -1
  98. package/dist/es5/dd-droppable.d.ts +1 -1
  99. package/dist/es5/dd-droppable.js +1 -1
  100. package/dist/es5/dd-droppable.js.map +1 -1
  101. package/dist/es5/dd-element.d.ts +1 -1
  102. package/dist/es5/dd-element.js +1 -1
  103. package/dist/es5/dd-element.js.map +1 -1
  104. package/dist/es5/dd-gridstack.d.ts +1 -1
  105. package/dist/es5/dd-gridstack.js +1 -1
  106. package/dist/es5/dd-gridstack.js.map +1 -1
  107. package/dist/es5/dd-manager.d.ts +1 -1
  108. package/dist/es5/dd-manager.js +1 -1
  109. package/dist/es5/dd-manager.js.map +1 -1
  110. package/dist/es5/dd-resizable-handle.d.ts +1 -1
  111. package/dist/es5/dd-resizable-handle.js +1 -1
  112. package/dist/es5/dd-resizable-handle.js.map +1 -1
  113. package/dist/es5/dd-resizable.d.ts +1 -1
  114. package/dist/es5/dd-resizable.js +1 -1
  115. package/dist/es5/dd-resizable.js.map +1 -1
  116. package/dist/es5/dd-touch.d.ts +1 -1
  117. package/dist/es5/dd-touch.js +1 -1
  118. package/dist/es5/dd-touch.js.map +1 -1
  119. package/dist/es5/gridstack-all.js +1 -1
  120. package/dist/es5/gridstack-all.js.LICENSE.txt +1 -1
  121. package/dist/es5/gridstack-all.js.map +1 -1
  122. package/dist/es5/gridstack-engine.d.ts +1 -1
  123. package/dist/es5/gridstack-engine.js +1 -1
  124. package/dist/es5/gridstack-engine.js.map +1 -1
  125. package/dist/es5/gridstack-poly.js +1 -1
  126. package/dist/es5/gridstack.d.ts +8 -5
  127. package/dist/es5/gridstack.js +13 -7
  128. package/dist/es5/gridstack.js.map +1 -1
  129. package/dist/es5/types.d.ts +1 -1
  130. package/dist/es5/types.js +1 -1
  131. package/dist/es5/types.js.map +1 -1
  132. package/dist/es5/utils.d.ts +1 -1
  133. package/dist/es5/utils.js +1 -1
  134. package/dist/es5/utils.js.map +1 -1
  135. package/dist/gridstack-all.js +1 -1
  136. package/dist/gridstack-all.js.LICENSE.txt +1 -1
  137. package/dist/gridstack-all.js.map +1 -1
  138. package/dist/gridstack-engine.d.ts +1 -1
  139. package/dist/gridstack-engine.js +1 -1
  140. package/dist/gridstack-engine.js.map +1 -1
  141. package/dist/gridstack.css +1 -1
  142. package/dist/gridstack.d.ts +8 -5
  143. package/dist/gridstack.js +13 -7
  144. package/dist/gridstack.js.map +1 -1
  145. package/dist/src/gridstack-extra.scss +3 -3
  146. package/dist/src/gridstack.scss +1 -1
  147. package/dist/types.d.ts +1 -1
  148. package/dist/types.js +1 -1
  149. package/dist/types.js.map +1 -1
  150. package/dist/utils.d.ts +1 -1
  151. package/dist/utils.js +1 -1
  152. package/dist/utils.js.map +1 -1
  153. package/doc/CHANGES.md +11 -1
  154. package/package.json +3 -3
  155. package/dist/ng/gridstack-item.component.js +0 -65
  156. package/dist/ng/gridstack-item.component.js.map +0 -1
  157. package/dist/ng/gridstack.component.js +0 -245
  158. package/dist/ng/gridstack.component.js.map +0 -1
@@ -0,0 +1,287 @@
1
+ /**
2
+ * gridstack.component.ts 8.2.0
3
+ * Copyright (c) 2022 Alain Dumesny - see GridStack root license
4
+ */
5
+
6
+ import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, Input,
7
+ OnDestroy, OnInit, Output, QueryList, Type, ViewChild, ViewContainerRef, reflectComponentType, ComponentRef } from '@angular/core';
8
+ import { Subject } from 'rxjs';
9
+ import { takeUntil } from 'rxjs/operators';
10
+ import { GridHTMLElement, GridItemHTMLElement, GridStack, GridStackNode, GridStackOptions, GridStackWidget } from 'gridstack';
11
+
12
+ import { GridItemCompHTMLElement, GridstackItemComponent } from './gridstack-item.component';
13
+ import { BaseWidget } from './base-widget';
14
+
15
+ /** events handlers emitters signature for different events */
16
+ export type eventCB = {event: Event};
17
+ export type elementCB = {event: Event, el: GridItemHTMLElement};
18
+ export type nodesCB = {event: Event, nodes: GridStackNode[]};
19
+ export type droppedCB = {event: Event, previousNode: GridStackNode, newNode: GridStackNode};
20
+
21
+ export type NgCompInputs = {[key: string]: any};
22
+
23
+ /** extends to store Ng Component selector, instead/inAddition to content */
24
+ export interface NgGridStackWidget extends GridStackWidget {
25
+ selector?: string; // component type to create as content
26
+ input?: NgCompInputs; // serialized data for the component input fields
27
+ }
28
+ export interface NgGridStackNode extends GridStackNode {
29
+ selector?: string; // component type to create as content
30
+ }
31
+ export interface NgGridStackOptions extends GridStackOptions {
32
+ children?: NgGridStackWidget[];
33
+ subGridOpts?: NgGridStackOptions;
34
+ }
35
+
36
+ /** store element to Ng Class pointer back */
37
+ export interface GridCompHTMLElement extends GridHTMLElement {
38
+ _gridComp?: GridstackComponent;
39
+ }
40
+
41
+ /** selector string to runtime Type mapping */
42
+ export type SelectorToType = {[key: string]: Type<Object>};
43
+
44
+ /**
45
+ * HTML Component Wrapper for gridstack, in combination with GridstackItemComponent for the items
46
+ */
47
+ @Component({
48
+ selector: 'gridstack',
49
+ template: `
50
+ <!-- content to show when when grid is empty, like instructions on how to add widgets -->
51
+ <ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
52
+ <!-- where dynamic items go -->
53
+ <ng-template #container></ng-template>
54
+ <!-- where template items go -->
55
+ <ng-content></ng-content>
56
+ `,
57
+ styles: [`
58
+ :host { display: block; }
59
+ `],
60
+ // changeDetection: ChangeDetectionStrategy.OnPush, // IFF you want to optimize and control when ChangeDetection needs to happen...
61
+ })
62
+ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
63
+
64
+ /** track list of TEMPLATE grid items so we can sync between DOM and GS internals */
65
+ @ContentChildren(GridstackItemComponent) public gridstackItems?: QueryList<GridstackItemComponent>;
66
+ /** container to append items dynamically */
67
+ @ViewChild('container', { read: ViewContainerRef, static: true}) public container?: ViewContainerRef;
68
+
69
+ /** initial options for creation of the grid */
70
+ @Input() public set options(val: GridStackOptions) { this._options = val; }
71
+ /** return the current running options */
72
+ public get options(): GridStackOptions { return this._grid?.opts || this._options || {}; }
73
+
74
+ /** true while ng-content with 'no-item-content' should be shown when last item is removed from a grid */
75
+ @Input() public isEmpty?: boolean;
76
+
77
+ /** individual list of GridStackEvent callbacks handlers as output
78
+ * otherwise use this.grid.on('name1 name2 name3', callback) to handle multiple at once
79
+ * see https://github.com/gridstack/gridstack.js/blob/master/demo/events.js#L4
80
+ *
81
+ * Note: camel casing and 'CB' added at the end to prevent @angular-eslint/no-output-native
82
+ * eg: 'change' would trigger the raw CustomEvent so use different name.
83
+ */
84
+ @Output() public addedCB = new EventEmitter<nodesCB>();
85
+ @Output() public changeCB = new EventEmitter<nodesCB>();
86
+ @Output() public disableCB = new EventEmitter<eventCB>();
87
+ @Output() public dragCB = new EventEmitter<elementCB>();
88
+ @Output() public dragStartCB = new EventEmitter<elementCB>();
89
+ @Output() public dragStopCB = new EventEmitter<elementCB>();
90
+ @Output() public droppedCB = new EventEmitter<droppedCB>();
91
+ @Output() public enableCB = new EventEmitter<eventCB>();
92
+ @Output() public removedCB = new EventEmitter<nodesCB>();
93
+ @Output() public resizeCB = new EventEmitter<elementCB>();
94
+ @Output() public resizeStartCB = new EventEmitter<elementCB>();
95
+ @Output() public resizeStopCB = new EventEmitter<elementCB>();
96
+
97
+ /** return the native element that contains grid specific fields as well */
98
+ public get el(): GridCompHTMLElement { return this.elementRef.nativeElement; }
99
+
100
+ /** return the GridStack class */
101
+ public get grid(): GridStack | undefined { return this._grid; }
102
+
103
+ /** ComponentRef of ourself - used by dynamic object to correctly get removed */
104
+ public ref: ComponentRef<GridstackComponent> | undefined;
105
+
106
+ /**
107
+ * stores the selector -> Type mapping, so we can create items dynamically from a string.
108
+ * Unfortunately Ng doesn't provide public access to that mapping.
109
+ */
110
+ public static selectorToType: SelectorToType = {};
111
+ /** add a list of ng Component to be mapped to selector */
112
+ public static addComponentToSelectorType(typeList: Array<Type<Object>>) {
113
+ typeList.forEach(type => GridstackComponent.selectorToType[ GridstackComponent.getSelector(type) ] = type);
114
+ }
115
+ /** return the ng Component selector */
116
+ public static getSelector(type: Type<Object>): string {
117
+ return reflectComponentType(type)!.selector;
118
+ }
119
+
120
+ private _options?: GridStackOptions;
121
+ private _grid?: GridStack;
122
+ private loaded?: boolean;
123
+ private ngUnsubscribe: Subject<void> = new Subject();
124
+
125
+ constructor(
126
+ // private readonly zone: NgZone,
127
+ // private readonly cd: ChangeDetectorRef,
128
+ private readonly elementRef: ElementRef<GridCompHTMLElement>,
129
+ ) {
130
+ this.el._gridComp = this;
131
+ }
132
+
133
+ public ngOnInit(): void {
134
+ // init ourself before any template children are created since we track them below anyway - no need to double create+update widgets
135
+ this.loaded = !!this.options?.children?.length;
136
+ this._grid = GridStack.init(this._options, this.el);
137
+ delete this._options; // GS has it now
138
+
139
+ this.checkEmpty();
140
+ }
141
+
142
+ /** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
143
+ public ngAfterContentInit(): void {
144
+ // track whenever the children list changes and update the layout...
145
+ this.gridstackItems?.changes
146
+ .pipe(takeUntil(this.ngUnsubscribe))
147
+ .subscribe(() => this.updateAll());
148
+ // ...and do this once at least unless we loaded children already
149
+ if (!this.loaded) this.updateAll();
150
+ this.hookEvents(this.grid);
151
+ }
152
+
153
+ public ngOnDestroy(): void {
154
+ delete this.ref;
155
+ this.ngUnsubscribe.next();
156
+ this.ngUnsubscribe.complete();
157
+ this.grid?.destroy();
158
+ delete this._grid;
159
+ delete this.el._gridComp;
160
+ }
161
+
162
+ /**
163
+ * called when the TEMPLATE list of items changes - get a list of nodes and
164
+ * update the layout accordingly (which will take care of adding/removing items changed by Angular)
165
+ */
166
+ public updateAll() {
167
+ if (!this.grid) return;
168
+ const layout: GridStackWidget[] = [];
169
+ this.gridstackItems?.forEach(item => {
170
+ layout.push(item.options);
171
+ item.clearOptions();
172
+ });
173
+ this.grid.load(layout); // efficient that does diffs only
174
+ }
175
+
176
+ /** check if the grid is empty, if so show alternative content */
177
+ public checkEmpty() {
178
+ if (!this.grid) return;
179
+ const isEmpty = !this.grid.engine.nodes.length;
180
+ if (isEmpty === this.isEmpty) return;
181
+ this.isEmpty = isEmpty;
182
+ // this.cd.detectChanges();
183
+ }
184
+
185
+ /** get all known events as easy to use Outputs for convenience */
186
+ private hookEvents(grid?: GridStack) {
187
+ if (!grid) return;
188
+ grid
189
+ .on('added', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.addedCB.emit({event, nodes}); })
190
+ .on('change', (event: Event, nodes: GridStackNode[]) => this.changeCB.emit({event, nodes}))
191
+ .on('disable', (event: Event) => this.disableCB.emit({event}))
192
+ .on('drag', (event: Event, el: GridItemHTMLElement) => this.dragCB.emit({event, el}))
193
+ .on('dragstart', (event: Event, el: GridItemHTMLElement) => this.dragStartCB.emit({event, el}))
194
+ .on('dragstop', (event: Event, el: GridItemHTMLElement) => this.dragStopCB.emit({event, el}))
195
+ .on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.droppedCB.emit({event, previousNode, newNode}))
196
+ .on('enable', (event: Event) => this.enableCB.emit({event}))
197
+ .on('removed', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.removedCB.emit({event, nodes}); })
198
+ .on('resize', (event: Event, el: GridItemHTMLElement) => this.resizeCB.emit({event, el}))
199
+ .on('resizestart', (event: Event, el: GridItemHTMLElement) => this.resizeStartCB.emit({event, el}))
200
+ .on('resizestop', (event: Event, el: GridItemHTMLElement) => this.resizeStopCB.emit({event, el}))
201
+ }
202
+ }
203
+
204
+ /**
205
+ * can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
206
+ **/
207
+ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridStackWidget | GridStackNode, add: boolean, isGrid: boolean): HTMLElement | undefined {
208
+ if (add) {
209
+ //
210
+ // create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
211
+ //
212
+ if (!host) return;
213
+ if (isGrid) {
214
+ const container = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp?.container;
215
+ // TODO: figure out how to create ng component inside regular Div. need to access app injectors...
216
+ // if (!container) {
217
+ // const hostElement: Element = host;
218
+ // const environmentInjector: EnvironmentInjector;
219
+ // grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
220
+ // }
221
+ const gridRef = container?.createComponent(GridstackComponent);
222
+ const grid = gridRef?.instance;
223
+ if (!grid) return;
224
+ grid.ref = gridRef;
225
+ grid.options = w as GridStackOptions;
226
+ return grid.el;
227
+ } else {
228
+ const gridComp = (host as GridCompHTMLElement)._gridComp;
229
+ const gridItemRef = gridComp?.container?.createComponent(GridstackItemComponent);
230
+ const gridItem = gridItemRef?.instance;
231
+ if (!gridItem) return;
232
+ gridItem.ref = gridItemRef
233
+
234
+ // IFF we're not a subGrid, define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
235
+ const selector = (w as NgGridStackWidget).selector;
236
+ const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
237
+ if (!w.subGridOpts && type) {
238
+ const childWidget = gridItem.container?.createComponent(type)?.instance as BaseWidget;
239
+ if (typeof childWidget?.serialize === 'function' && typeof childWidget?.deserialize === 'function') {
240
+ // proper BaseWidget subclass, save it and load additional data
241
+ gridItem.childWidget = childWidget;
242
+ childWidget.deserialize(w);
243
+ }
244
+ }
245
+
246
+ return gridItem.el;
247
+ }
248
+ } else {
249
+ //
250
+ // REMOVE - have to call ComponentRef:destroy() for dynamic objects to correctly remove themselves
251
+ // Note: this will destroy all children dynamic components as well: gridItem -> childWidget
252
+ //
253
+ const n = w as GridStackNode;
254
+ if (isGrid) {
255
+ const grid = (n.el as GridCompHTMLElement)?._gridComp;
256
+ if (grid?.ref) grid.ref.destroy();
257
+ else grid?.ngOnDestroy();
258
+ } else {
259
+ const gridItem = (n.el as GridItemCompHTMLElement)?._gridItemComp;
260
+ if (gridItem?.ref) gridItem.ref.destroy();
261
+ else gridItem?.ngOnDestroy();
262
+ }
263
+ }
264
+ return;
265
+ }
266
+
267
+ /**
268
+ * called for each item in the grid - check if additional information needs to be saved.
269
+ * Note: since this is options minus gridstack private members using Utils.removeInternalForSave(),
270
+ * this typically doesn't need to do anything. However your custom Component @Input() are now supported
271
+ * using BaseWidget.serialize()
272
+ */
273
+ export function gsSaveAdditionalNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
274
+ const gridItem = (n.el as GridItemCompHTMLElement)?._gridItemComp;
275
+ if (gridItem) {
276
+ const input = gridItem.childWidget?.serialize();
277
+ if (input) {
278
+ w.input = input;
279
+ }
280
+ return;
281
+ }
282
+ // else check if Grid
283
+ const grid = (n.el as GridCompHTMLElement)?._gridComp;
284
+ if (grid) {
285
+ //.... save any custom data
286
+ }
287
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * gridstack.component.ts 8.2.0
3
+ * Copyright (c) 2022 Alain Dumesny - see GridStack root license
4
+ */
5
+
6
+ import { NgModule } from "@angular/core";
7
+ import { CommonModule } from '@angular/common';
8
+
9
+ import { GridStack } from "gridstack";
10
+ import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionalNgInfo } from "./gridstack.component";
11
+ import { GridstackItemComponent } from "./gridstack-item.component";
12
+
13
+ @NgModule({
14
+ imports: [
15
+ CommonModule,
16
+ ],
17
+ declarations: [
18
+ GridstackComponent,
19
+ GridstackItemComponent,
20
+ ],
21
+ exports: [
22
+ GridstackComponent,
23
+ GridstackItemComponent,
24
+ ],
25
+ })
26
+ export class GridstackModule {
27
+ constructor() {
28
+ // set globally our method to create the right widget type
29
+ GridStack.addRemoveCB = gsCreateNgComponents;
30
+ GridStack.saveCB = gsSaveAdditionalNgInfo;
31
+ }
32
+ }
@@ -0,0 +1,8 @@
1
+ /*
2
+ * Public API Surface of gridstack-angular
3
+ */
4
+
5
+ export * from './lib/gridstack-item.component';
6
+ export * from './lib/gridstack.component';
7
+ export * from './lib/base-widget';
8
+ export * from './lib/gridstack.module';
@@ -0,0 +1,27 @@
1
+ // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2
+
3
+ import 'zone.js';
4
+ import 'zone.js/testing';
5
+ import { getTestBed } from '@angular/core/testing';
6
+ import {
7
+ BrowserDynamicTestingModule,
8
+ platformBrowserDynamicTesting
9
+ } from '@angular/platform-browser-dynamic/testing';
10
+
11
+ declare const require: {
12
+ context(path: string, deep?: boolean, filter?: RegExp): {
13
+ <T>(id: string): T;
14
+ keys(): string[];
15
+ };
16
+ };
17
+
18
+ // First, initialize the Angular testing environment.
19
+ getTestBed().initTestEnvironment(
20
+ BrowserDynamicTestingModule,
21
+ platformBrowserDynamicTesting(),
22
+ );
23
+
24
+ // Then we find all the tests.
25
+ const context = require.context('./', true, /\.spec\.ts$/);
26
+ // And load the modules.
27
+ context.keys().forEach(context);
@@ -0,0 +1,15 @@
1
+ /* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+ {
3
+ "extends": "../../tsconfig.json",
4
+ "compilerOptions": {
5
+ "outDir": "../../out-tsc/lib",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "inlineSources": true,
9
+ "types": []
10
+ },
11
+ "exclude": [
12
+ "src/test.ts",
13
+ "**/*.spec.ts"
14
+ ]
15
+ }
@@ -0,0 +1,10 @@
1
+ /* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+ {
3
+ "extends": "./tsconfig.lib.json",
4
+ "compilerOptions": {
5
+ "declarationMap": false
6
+ },
7
+ "angularCompilerOptions": {
8
+ "compilationMode": "partial"
9
+ }
10
+ }
@@ -0,0 +1,17 @@
1
+ /* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+ {
3
+ "extends": "../../tsconfig.json",
4
+ "compilerOptions": {
5
+ "outDir": "../../out-tsc/spec",
6
+ "types": [
7
+ "jasmine"
8
+ ]
9
+ },
10
+ "files": [
11
+ "src/test.ts"
12
+ ],
13
+ "include": [
14
+ "**/*.spec.ts",
15
+ "**/*.d.ts"
16
+ ]
17
+ }
@@ -0,0 +1,170 @@
1
+ # Angular wrapper
2
+
3
+ The Angular [wrapper component](projects/lib/src/lib/gridstack.component.ts) <gridstack> is a better way to use Gridstack, but alternative raw [ngFor](projects/demo/src/app/ngFor.ts) or [simple](projects/demo/src/app/simple.ts) demos are also given.
4
+
5
+ # Dynamic grid items
6
+ this is the recommended way if you are going to have multiple grids (alow drag&drop between) or drag from toolbar to create items, or drag to remove items, etc...
7
+
8
+ I.E. don't use Angular templating to create grid items as that is harder to sync when gridstack will also add/remove items.
9
+
10
+ HTML
11
+ ```html
12
+ <gridstack [options]="gridOptions">
13
+ </gridstack>
14
+ ```
15
+
16
+ CSS
17
+ ```css
18
+ @import "gridstack/dist/gridstack.min.css";
19
+
20
+ .grid-stack {
21
+ background: #FAFAD2;
22
+ }
23
+ .grid-stack-item-content {
24
+ text-align: center;
25
+ background-color: #18bc9c;
26
+ }
27
+ ```
28
+
29
+ in your module Code
30
+ ```ts
31
+ import { GridstackModule } from 'gridstack/dist/angular';
32
+
33
+ @NgModule({
34
+ imports: [GridstackModule, ...]
35
+ ...
36
+ bootstrap: [AppComponent]
37
+ })
38
+ export class AppModule { }
39
+ ```
40
+
41
+ Component Code
42
+ ```ts
43
+ import { GridStackOptions } from 'gridstack';
44
+
45
+ // sample grid options + items to load...
46
+ public gridOptions: GridStackOptions = {
47
+ margin: 5,
48
+ float: true,
49
+ children: [ // or call load()/addWidget() with same data
50
+ {x:0, y:0, minW:2, content:'Item 1'},
51
+ {x:1, y:1, content:'Item 2'},
52
+ {x:2, y:2, content:'Item 3'},
53
+ ]
54
+ }
55
+ ```
56
+
57
+ # More Complete example
58
+ In this example (build on previous one) will use your actual custom angular components inside each grid item (instead of dummy html content)
59
+
60
+ HTML
61
+ ```html
62
+ <gridstack [options]="gridOptions" (changeCB)="onChange($event)">
63
+ <div empty-content>message when grid is empty</div>
64
+ </gridstack>
65
+ ```
66
+
67
+ Code
68
+ ```ts
69
+ import { Component } from '@angular/core';
70
+ import { GridStack, GridStackOptions } from 'gridstack';
71
+ import { GridstackComponent, gsCreateNgComponents, NgGridStackWidget, nodesCB } from 'gridstack/dist/angular';
72
+
73
+ // some custom components
74
+ @Component({
75
+ selector: 'app-a',
76
+ template: 'Comp A', // your real ng content goes in each component instead...
77
+ })
78
+ export class AComponent {
79
+ }
80
+
81
+ @Component({
82
+ selector: 'app-b',
83
+ template: 'Comp B',
84
+ })
85
+ export class BComponent {
86
+ }
87
+
88
+ // .... in your module for example
89
+ constructor() {
90
+ // register all our dynamic components types created by the grid
91
+ GridstackComponent.addComponentToSelectorType([AComponent, BComponent]);
92
+ }
93
+
94
+ // now our content will use Components instead of dummy html content
95
+ public gridOptions: NgGridStackOptions = {
96
+ margin: 5,
97
+ float: true,
98
+ minRow: 1, // make space for empty message
99
+ children: [ // or call load()/addWidget() with same data
100
+ {x:0, y:0, minW:2, type:'app-a'},
101
+ {x:1, y:1, type:'app-b'},
102
+ {x:2, y:2, content:'plain html content'},
103
+ ]
104
+ }
105
+
106
+ // called whenever items change size/position/etc.. see other events
107
+ public onChange(data: nodesCB) {
108
+ console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
109
+ }
110
+ ```
111
+
112
+ # ngFor with wrapper
113
+ For simple case where you control the children creation (gridstack doesn't do create or re-parenting)
114
+
115
+ HTML
116
+ ```html
117
+ <gridstack [options]="gridOptions" (changeCB)="onChange($event)">
118
+ <gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
119
+ Item {{n.id}}
120
+ </gridstack-item>
121
+ </gridstack>
122
+ ```
123
+
124
+ Code
125
+ ```javascript
126
+ import { GridStackOptions, GridStackWidget } from 'gridstack';
127
+ import { nodesCB } from 'gridstack/dist/angular';
128
+
129
+ /** sample grid options and items to load... */
130
+ public gridOptions: GridStackOptions = {
131
+ margin: 5,
132
+ float: true,
133
+ }
134
+ public items: GridStackWidget[] = [
135
+ {x:0, y:0, minW:2, id:'1'}, // must have unique id used for trackBy
136
+ {x:1, y:1, id:'2'},
137
+ {x:2, y:2, id:'3'},
138
+ ];
139
+
140
+ // called whenever items change size/position/etc..
141
+ public onChange(data: nodesCB) {
142
+ console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
143
+ }
144
+
145
+ // ngFor unique node id to have correct match between our items used and GS
146
+ public identify(index: number, w: GridStackWidget) {
147
+ return w.id; // or use index if no id is set and you only modify at the end...
148
+ }
149
+ ```
150
+
151
+ ## Demo
152
+ You can see a fuller example at [app.component.ts](src/app/app.component.ts)
153
+
154
+ to build the demo, go to angular/projects/demo and run `yarn` + `yarn start` and navigate to `http://localhost:4200/`
155
+
156
+ Code now shipped starting with v8.1.2+ in `dist/angular` for people to use directly! (source code under `dist/angular/src`)
157
+ ## Caveats
158
+
159
+ - This wrapper needs:
160
+ - gridstack v8 to run as it needs the latest changes (use older version that matches GS versions)
161
+ - Angular 13+ for dynamic createComponent() API
162
+
163
+ ## *ngFor Caveats
164
+ - This wrapper handles well ngFor loops, but if you're using a trackBy function (as I would recommend) and no element id change after an update,
165
+ you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](src/app/app.component.ts#L174) example.
166
+ - The original client list of items is not updated to match **content** changes made by gridstack (TBD later), but adding new item or removing (as shown in demo) will update those new items. Client could use change/added/removed events to sync that list if they wish to do so.
167
+
168
+ Would appreciate getting help doing the same for React and Vue (2 other popular frameworks)
169
+
170
+ -Alain
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JpZHN0YWNrLWFuZ3VsYXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvZ3JpZHN0YWNrLWFuZ3VsYXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
@@ -0,0 +1,30 @@
1
+ /**
2
+ * gridstack-item.component.ts 8.2.0
3
+ * Copyright (c) 2022 Alain Dumesny - see GridStack root license
4
+ */
5
+ /**
6
+ * Base interface that all widgets need to implement in order for GridstackItemComponent to correctly save/load/delete/..
7
+ */
8
+ import { Injectable } from '@angular/core';
9
+ import * as i0 from "@angular/core";
10
+ export class BaseWidget {
11
+ /**
12
+ * REDEFINE to return an object representing the data needed to re-create yourself, other than `selector` already handled.
13
+ * This should map directly to the @Input() fields of this objects on create, so a simple apply can be used on read
14
+ */
15
+ serialize() { return; }
16
+ /**
17
+ * REDEFINE this if your widget needs to read from saved data and transform it to create itself - you do this for
18
+ * things that are not mapped directly into @Input() fields for example.
19
+ */
20
+ deserialize(w) {
21
+ if (w.input)
22
+ Object.assign(this, w.input);
23
+ }
24
+ }
25
+ BaseWidget.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
26
+ BaseWidget.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget });
27
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, decorators: [{
28
+ type: Injectable
29
+ }] });
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS13aWRnZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvbGliL2Jhc2Utd2lkZ2V0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUVIOztHQUVHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQzs7QUFJMUMsTUFBTSxPQUFnQixVQUFVO0lBQy9COzs7T0FHRztJQUNJLFNBQVMsS0FBZ0MsT0FBTyxDQUFDLENBQUM7SUFFekQ7OztPQUdHO0lBQ0ksV0FBVyxDQUFDLENBQW9CO1FBQ3JDLElBQUksQ0FBQyxDQUFDLEtBQUs7WUFBRyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDN0MsQ0FBQzs7dUdBYm9CLFVBQVU7MkdBQVYsVUFBVTsyRkFBVixVQUFVO2tCQUQvQixVQUFVIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXHJcbiAqIGdyaWRzdGFjay1pdGVtLmNvbXBvbmVudC50cyA4LjIuMFxyXG4gKiBDb3B5cmlnaHQgKGMpIDIwMjIgQWxhaW4gRHVtZXNueSAtIHNlZSBHcmlkU3RhY2sgcm9vdCBsaWNlbnNlXHJcbiAqL1xyXG5cclxuLyoqXHJcbiAqIEJhc2UgaW50ZXJmYWNlIHRoYXQgYWxsIHdpZGdldHMgbmVlZCB0byBpbXBsZW1lbnQgaW4gb3JkZXIgZm9yIEdyaWRzdGFja0l0ZW1Db21wb25lbnQgdG8gY29ycmVjdGx5IHNhdmUvbG9hZC9kZWxldGUvLi5cclxuICovXHJcblxyXG5pbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IE5nQ29tcElucHV0cywgTmdHcmlkU3RhY2tXaWRnZXQgfSBmcm9tICcuL2dyaWRzdGFjay5jb21wb25lbnQnO1xyXG5cclxuIEBJbmplY3RhYmxlKClcclxuIGV4cG9ydCBhYnN0cmFjdCBjbGFzcyBCYXNlV2lkZ2V0IHtcclxuICAvKipcclxuICAgKiBSRURFRklORSB0byByZXR1cm4gYW4gb2JqZWN0IHJlcHJlc2VudGluZyB0aGUgZGF0YSBuZWVkZWQgdG8gcmUtY3JlYXRlIHlvdXJzZWxmLCBvdGhlciB0aGFuIGBzZWxlY3RvcmAgYWxyZWFkeSBoYW5kbGVkLlxyXG4gICAqIFRoaXMgc2hvdWxkIG1hcCBkaXJlY3RseSB0byB0aGUgQElucHV0KCkgZmllbGRzIG9mIHRoaXMgb2JqZWN0cyBvbiBjcmVhdGUsIHNvIGEgc2ltcGxlIGFwcGx5IGNhbiBiZSB1c2VkIG9uIHJlYWRcclxuICAgKi9cclxuICBwdWJsaWMgc2VyaWFsaXplKCk6IE5nQ29tcElucHV0cyB8IHVuZGVmaW5lZCAgeyByZXR1cm47IH1cclxuXHJcbiAgLyoqXHJcbiAgICogUkVERUZJTkUgdGhpcyBpZiB5b3VyIHdpZGdldCBuZWVkcyB0byByZWFkIGZyb20gc2F2ZWQgZGF0YSBhbmQgdHJhbnNmb3JtIGl0IHRvIGNyZWF0ZSBpdHNlbGYgLSB5b3UgZG8gdGhpcyBmb3JcclxuICAgKiB0aGluZ3MgdGhhdCBhcmUgbm90IG1hcHBlZCBkaXJlY3RseSBpbnRvIEBJbnB1dCgpIGZpZWxkcyBmb3IgZXhhbXBsZS5cclxuICAgKi9cclxuICBwdWJsaWMgZGVzZXJpYWxpemUodzogTmdHcmlkU3RhY2tXaWRnZXQpICB7XHJcbiAgICBpZiAody5pbnB1dCkgIE9iamVjdC5hc3NpZ24odGhpcywgdy5pbnB1dCk7XHJcbiAgfVxyXG4gfVxyXG4iXX0=