Use npm for gridstack, remove unnecessary folders using our dedicated service

This commit is contained in:
Stephen Abello
2026-01-09 15:07:34 +01:00
parent 1538596db8
commit 7b193dd737
134 changed files with 405 additions and 15344 deletions

16
node_modules/.package-lock.json generated vendored
View File

@@ -186,6 +186,22 @@
"delegate": "^3.1.2"
}
},
"node_modules/gridstack": {
"version": "12.4.2",
"resolved": "https://registry.npmjs.org/gridstack/-/gridstack-12.4.2.tgz",
"integrity": "sha512-aXbJrQpi3LwpYXYOr4UriPM5uc/dPcjK01SdOE5PDpx2vi8tnLhU7yBg/1i4T59UhNkG/RBfabdFUObuN+gMnw==",
"funding": [
{
"type": "paypal",
"url": "https://www.paypal.me/alaind831"
},
{
"type": "venmo",
"url": "https://www.venmo.com/adumesny"
}
],
"license": "MIT"
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",

View File

@@ -1,211 +0,0 @@
# Angular wrapper
The Angular [wrapper component](projects/lib/src/lib/gridstack.component.ts) <gridstack> is a <b>better way to use Gridstack</b>, but alternative raw [ngFor](projects/demo/src/app/ngFor.ts) or [simple](projects/demo/src/app/simple.ts) demos are also given.
Running version can be seen here https://stackblitz.com/edit/gridstack-angular
# Dynamic grid items
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...
I.E. don't use Angular templating to create grid items as that is harder to sync when gridstack will also add/remove items.
MyComponent HTML
```html
<gridstack [options]="gridOptions"></gridstack>
```
MyComponent CSS
```css
@import "gridstack/dist/gridstack.min.css";
.grid-stack {
background: #fafad2;
}
.grid-stack-item-content {
text-align: center;
background-color: #18bc9c;
}
```
Standalone MyComponent Code
```ts
import { GridStackOptions } from 'gridstack';
import { GridstackComponent, GridstackItemComponent } from 'gridstack/dist/angular';
@Component({
imports: [ // SKIP if doing module import instead (next)
GridstackComponent,
GridstackItemComponent
]
...
})
export class MyComponent {
// sample grid options + items to load...
public gridOptions: GridStackOptions = {
margin: 5,
children: [ // or call load(children) or addWidget(children[0]) with same data
{x:0, y:0, minW:2, content:'Item 1'},
{x:1, y:0, content:'Item 2'},
{x:0, y:1, content:'Item 3'},
]
}
}
```
IF doing module import instead of standalone, you will also need this:
```ts
import { GridstackModule } from 'gridstack/dist/angular';
@NgModule({
imports: [GridstackModule, ...]
...
bootstrap: [AppComponent]
})
export class AppModule { }
```
# More Complete example
In this example (build on previous one) will use your actual custom angular components inside each grid item (instead of dummy html content) and have per component saved settings as well (using BaseWidget).
HTML
```html
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
<div empty-content>message when grid is empty</div>
</gridstack>
```
Code
```ts
import { Component } from '@angular/core';
import { GridStack, GridStackOptions } from 'gridstack';
import { GridstackComponent, gsCreateNgComponents, NgGridStackWidget, nodesCB, BaseWidget } from 'gridstack/dist/angular';
// some custom components
@Component({
selector: 'app-a',
template: 'Comp A {{text}}',
})
export class AComponent extends BaseWidget implements OnDestroy {
@Input() text: string = 'foo'; // test custom input data
public override serialize(): NgCompInputs | undefined { return this.text ? {text: this.text} : undefined; }
ngOnDestroy() {
console.log('Comp A destroyed'); // test to make sure cleanup happens
}
}
@Component({
selector: 'app-b',
template: 'Comp B',
})
export class BComponent extends BaseWidget {
}
// ...in your module (classic), OR your ng19 app.config provideEnvironmentInitializer call this:
constructor() {
// register all our dynamic components types created by the grid
GridstackComponent.addComponentToSelectorType([AComponent, BComponent]) ;
}
// now our content will use Components instead of dummy html content
public gridOptions: NgGridStackOptions = {
margin: 5,
minRow: 1, // make space for empty message
children: [ // or call load()/addWidget() with same data
{x:0, y:0, minW:2, selector:'app-a'},
{x:1, y:0, minW:2, selector:'app-a', input: { text: 'bar' }}, // custom input that works using BaseWidget.deserialize() Object.assign(this, w.input)
{x:2, y:0, selector:'app-b'},
{x:3, y:0, content:'plain html'},
]
}
// called whenever items change size/position/etc.. see other events
public onChange(data: nodesCB) {
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
}
```
# ngFor with wrapper
For simple case where you control the children creation (gridstack doesn't do create or re-parenting)
HTML
```html
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
<!-- Angular 17+ -->
@for (n of items; track n.id) {
<gridstack-item [options]="n">Item {{n.id}}</gridstack-item>
}
<!-- Angular 16 -->
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n"> Item {{n.id}} </gridstack-item>
</gridstack>
```
Code
```javascript
import { GridStackOptions, GridStackWidget } from 'gridstack';
import { nodesCB } from 'gridstack/dist/angular';
/** sample grid options and items to load... */
public gridOptions: GridStackOptions = { margin: 5 }
public items: GridStackWidget[] = [
{x:0, y:0, minW:2, id:'1'}, // must have unique id used for trackBy
{x:1, y:0, id:'2'},
{x:0, y:1, id:'3'},
];
// called whenever items change size/position/etc..
public onChange(data: nodesCB) {
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
}
// ngFor unique node id to have correct match between our items used and GS
public identify(index: number, w: GridStackWidget) {
return w.id; // or use index if no id is set and you only modify at the end...
}
```
## Demo
You can see a fuller example at [app.component.ts](projects/demo/src/app/app.component.ts)
to build the demo, go to [angular/projects/demo](projects/demo/) and run `yarn` + `yarn start` and navigate to `http://localhost:4200/`
Code started shipping with v8.1.2+ in `dist/angular` for people to use directly and is an angular module! (source code under `dist/angular/src`)
## Caveats
- This wrapper needs:
- gridstack v8+ to run as it needs the latest changes (use older version that matches GS versions)
- <b>Angular 14+</b> for dynamic `createComponent()` API and Standalone Components (verified against 19+)
NOTE: if you are on Angular 13 or below: copy the wrapper code over (or patch it - see main page example) and change `createComponent()` calls to use old API instead:
NOTE2: now that we're using standalone, you will also need to remove `standalone: true` and `imports` on each component so you will to copy those locally (or use <11.1.2 version)
```ts
protected resolver: ComponentFactoryResolver,
...
const factory = this.resolver.resolveComponentFactory(GridItemComponent);
const gridItemRef = grid.container.createComponent(factory) as ComponentRef<GridItemComponent>;
// ...do the same for widget selector...
```
## ngFor Caveats
- 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,
you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./projects/demo/src/app/app.component.ts#L202) example.
- 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.
Would appreciate getting help doing the same for React and Vue (2 other popular frameworks)
-Alain

View File

@@ -1,5 +0,0 @@
/**
* Generated bundle index. Do not edit.
*/
export * from './index';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JpZHN0YWNrLWFuZ3VsYXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvZ3JpZHN0YWNrLWFuZ3VsYXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=

View File

@@ -1,9 +0,0 @@
/*
* Public API Surface of gridstack-angular
*/
export * from './lib/types';
export * from './lib/base-widget';
export * from './lib/gridstack-item.component';
export * from './lib/gridstack.component';
export * from './lib/gridstack.module';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLHdCQUF3QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLypcbiAqIFB1YmxpYyBBUEkgU3VyZmFjZSBvZiBncmlkc3RhY2stYW5ndWxhclxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vbGliL3R5cGVzJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2Jhc2Utd2lkZ2V0JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2dyaWRzdGFjay1pdGVtLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9ncmlkc3RhY2suY29tcG9uZW50JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2dyaWRzdGFjay5tb2R1bGUnO1xuIl19

View File

@@ -1,90 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Abstract base class that all custom widgets should extend.
*
* This class provides the interface needed for GridstackItemComponent to:
* - Serialize/deserialize widget data
* - Save/restore widget state
* - Integrate with Angular lifecycle
*
* Extend this class when creating custom widgets for dynamic grids.
*
* @example
* ```typescript
* @Component({
* selector: 'my-custom-widget',
* template: '<div>{{data}}</div>'
* })
* export class MyCustomWidget extends BaseWidget {
* @Input() data: string = '';
*
* serialize() {
* return { data: this.data };
* }
* }
* ```
*/
import { Injectable } from '@angular/core';
import * as i0 from "@angular/core";
/**
* Base widget class for GridStack Angular integration.
*/
export class BaseWidget {
/**
* Override this method to return serializable data for this widget.
*
* Return an object with properties that map to your component's @Input() fields.
* The selector is handled automatically, so only include component-specific data.
*
* @returns Object containing serializable component data
*
* @example
* ```typescript
* serialize() {
* return {
* title: this.title,
* value: this.value,
* settings: this.settings
* };
* }
* ```
*/
serialize() { return; }
/**
* Override this method to handle widget restoration from saved data.
*
* Use this for complex initialization that goes beyond simple @Input() mapping.
* The default implementation automatically assigns input data to component properties.
*
* @param w The saved widget data including input properties
*
* @example
* ```typescript
* deserialize(w: NgGridStackWidget) {
* super.deserialize(w); // Call parent for basic setup
*
* // Custom initialization logic
* if (w.input?.complexData) {
* this.processComplexData(w.input.complexData);
* }
* }
* ```
*/
deserialize(w) {
// save full description for meta data
this.widgetItem = w;
if (!w)
return;
if (w.input)
Object.assign(this, w.input);
}
}
BaseWidget.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
BaseWidget.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, decorators: [{
type: Injectable
}] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS13aWRnZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvbGliL2Jhc2Utd2lkZ2V0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUVIOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFFSCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDOztBQUczQzs7R0FFRztBQUVILE1BQU0sT0FBZ0IsVUFBVTtJQVE5Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7O09Ba0JHO0lBQ0ksU0FBUyxLQUFnQyxPQUFPLENBQUMsQ0FBQztJQUV6RDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQW1CRztJQUNJLFdBQVcsQ0FBQyxDQUFvQjtRQUNyQyxzQ0FBc0M7UUFDdEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDcEIsSUFBSSxDQUFDLENBQUM7WUFBRSxPQUFPO1FBRWYsSUFBSSxDQUFDLENBQUMsS0FBSztZQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUM3QyxDQUFDOzt1R0F2RG1CLFVBQVU7MkdBQVYsVUFBVTsyRkFBVixVQUFVO2tCQUQvQixVQUFVIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXHJcbiAqIGdyaWRzdGFjay1pdGVtLmNvbXBvbmVudC50cyAxMi4zLjNcclxuICogQ29weXJpZ2h0IChjKSAyMDIyLTIwMjQgQWxhaW4gRHVtZXNueSAtIHNlZSBHcmlkU3RhY2sgcm9vdCBsaWNlbnNlXHJcbiAqL1xyXG5cclxuLyoqXHJcbiAqIEFic3RyYWN0IGJhc2UgY2xhc3MgdGhhdCBhbGwgY3VzdG9tIHdpZGdldHMgc2hvdWxkIGV4dGVuZC5cclxuICogXHJcbiAqIFRoaXMgY2xhc3MgcHJvdmlkZXMgdGhlIGludGVyZmFjZSBuZWVkZWQgZm9yIEdyaWRzdGFja0l0ZW1Db21wb25lbnQgdG86XHJcbiAqIC0gU2VyaWFsaXplL2Rlc2VyaWFsaXplIHdpZGdldCBkYXRhXHJcbiAqIC0gU2F2ZS9yZXN0b3JlIHdpZGdldCBzdGF0ZVxyXG4gKiAtIEludGVncmF0ZSB3aXRoIEFuZ3VsYXIgbGlmZWN5Y2xlXHJcbiAqIFxyXG4gKiBFeHRlbmQgdGhpcyBjbGFzcyB3aGVuIGNyZWF0aW5nIGN1c3RvbSB3aWRnZXRzIGZvciBkeW5hbWljIGdyaWRzLlxyXG4gKiBcclxuICogQGV4YW1wbGVcclxuICogYGBgdHlwZXNjcmlwdFxyXG4gKiBAQ29tcG9uZW50KHtcclxuICogICBzZWxlY3RvcjogJ215LWN1c3RvbS13aWRnZXQnLFxyXG4gKiAgIHRlbXBsYXRlOiAnPGRpdj57e2RhdGF9fTwvZGl2PidcclxuICogfSlcclxuICogZXhwb3J0IGNsYXNzIE15Q3VzdG9tV2lkZ2V0IGV4dGVuZHMgQmFzZVdpZGdldCB7XHJcbiAqICAgQElucHV0KCkgZGF0YTogc3RyaW5nID0gJyc7XHJcbiAqICAgXHJcbiAqICAgc2VyaWFsaXplKCkge1xyXG4gKiAgICAgcmV0dXJuIHsgZGF0YTogdGhpcy5kYXRhIH07XHJcbiAqICAgfVxyXG4gKiB9XHJcbiAqIGBgYFxyXG4gKi9cclxuXHJcbmltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgTmdDb21wSW5wdXRzLCBOZ0dyaWRTdGFja1dpZGdldCB9IGZyb20gJy4vdHlwZXMnO1xyXG5cclxuLyoqXHJcbiAqIEJhc2Ugd2lkZ2V0IGNsYXNzIGZvciBHcmlkU3RhY2sgQW5ndWxhciBpbnRlZ3JhdGlvbi5cclxuICovXHJcbkBJbmplY3RhYmxlKClcclxuZXhwb3J0IGFic3RyYWN0IGNsYXNzIEJhc2VXaWRnZXQge1xyXG5cclxuICAvKipcclxuICAgKiBDb21wbGV0ZSB3aWRnZXQgZGVmaW5pdGlvbiBpbmNsdWRpbmcgcG9zaXRpb24sIHNpemUsIGFuZCBBbmd1bGFyLXNwZWNpZmljIGRhdGEuXHJcbiAgICogUG9wdWxhdGVkIGF1dG9tYXRpY2FsbHkgd2hlbiB0aGUgd2lkZ2V0IGlzIGxvYWRlZCBvciBzYXZlZC5cclxuICAgKi9cclxuICBwdWJsaWMgd2lkZ2V0SXRlbT86IE5nR3JpZFN0YWNrV2lkZ2V0O1xyXG5cclxuICAvKipcclxuICAgKiBPdmVycmlkZSB0aGlzIG1ldGhvZCB0byByZXR1cm4gc2VyaWFsaXphYmxlIGRhdGEgZm9yIHRoaXMgd2lkZ2V0LlxyXG4gICAqIFxyXG4gICAqIFJldHVybiBhbiBvYmplY3Qgd2l0aCBwcm9wZXJ0aWVzIHRoYXQgbWFwIHRvIHlvdXIgY29tcG9uZW50J3MgQElucHV0KCkgZmllbGRzLlxyXG4gICAqIFRoZSBzZWxlY3RvciBpcyBoYW5kbGVkIGF1dG9tYXRpY2FsbHksIHNvIG9ubHkgaW5jbHVkZSBjb21wb25lbnQtc3BlY2lmaWMgZGF0YS5cclxuICAgKiBcclxuICAgKiBAcmV0dXJucyBPYmplY3QgY29udGFpbmluZyBzZXJpYWxpemFibGUgY29tcG9uZW50IGRhdGFcclxuICAgKiBcclxuICAgKiBAZXhhbXBsZVxyXG4gICAqIGBgYHR5cGVzY3JpcHRcclxuICAgKiBzZXJpYWxpemUoKSB7XHJcbiAgICogICByZXR1cm4ge1xyXG4gICAqICAgICB0aXRsZTogdGhpcy50aXRsZSxcclxuICAgKiAgICAgdmFsdWU6IHRoaXMudmFsdWUsXHJcbiAgICogICAgIHNldHRpbmdzOiB0aGlzLnNldHRpbmdzXHJcbiAgICogICB9O1xyXG4gICAqIH1cclxuICAgKiBgYGBcclxuICAgKi9cclxuICBwdWJsaWMgc2VyaWFsaXplKCk6IE5nQ29tcElucHV0cyB8IHVuZGVmaW5lZCAgeyByZXR1cm47IH1cclxuXHJcbiAgLyoqXHJcbiAgICogT3ZlcnJpZGUgdGhpcyBtZXRob2QgdG8gaGFuZGxlIHdpZGdldCByZXN0b3JhdGlvbiBmcm9tIHNhdmVkIGRhdGEuXHJcbiAgICogXHJcbiAgICogVXNlIHRoaXMgZm9yIGNvbXBsZXggaW5pdGlhbGl6YXRpb24gdGhhdCBnb2VzIGJleW9uZCBzaW1wbGUgQElucHV0KCkgbWFwcGluZy5cclxuICAgKiBUaGUgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBhdXRvbWF0aWNhbGx5IGFzc2lnbnMgaW5wdXQgZGF0YSB0byBjb21wb25lbnQgcHJvcGVydGllcy5cclxuICAgKiBcclxuICAgKiBAcGFyYW0gdyBUaGUgc2F2ZWQgd2lkZ2V0IGRhdGEgaW5jbHVkaW5nIGlucHV0IHByb3BlcnRpZXNcclxuICAgKiBcclxuICAgKiBAZXhhbXBsZVxyXG4gICAqIGBgYHR5cGVzY3JpcHRcclxuICAgKiBkZXNlcmlhbGl6ZSh3OiBOZ0dyaWRTdGFja1dpZGdldCkge1xyXG4gICAqICAgc3VwZXIuZGVzZXJpYWxpemUodyk7IC8vIENhbGwgcGFyZW50IGZvciBiYXNpYyBzZXR1cFxyXG4gICAqICAgXHJcbiAgICogICAvLyBDdXN0b20gaW5pdGlhbGl6YXRpb24gbG9naWNcclxuICAgKiAgIGlmICh3LmlucHV0Py5jb21wbGV4RGF0YSkge1xyXG4gICAqICAgICB0aGlzLnByb2Nlc3NDb21wbGV4RGF0YSh3LmlucHV0LmNvbXBsZXhEYXRhKTtcclxuICAgKiAgIH1cclxuICAgKiB9XHJcbiAgICogYGBgXHJcbiAgICovXHJcbiAgcHVibGljIGRlc2VyaWFsaXplKHc6IE5nR3JpZFN0YWNrV2lkZ2V0KSAge1xyXG4gICAgLy8gc2F2ZSBmdWxsIGRlc2NyaXB0aW9uIGZvciBtZXRhIGRhdGFcclxuICAgIHRoaXMud2lkZ2V0SXRlbSA9IHc7XHJcbiAgICBpZiAoIXcpIHJldHVybjtcclxuXHJcbiAgICBpZiAody5pbnB1dCkgIE9iamVjdC5hc3NpZ24odGhpcywgdy5pbnB1dCk7XHJcbiAgfVxyXG4gfVxyXG4iXX0=

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,53 +0,0 @@
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import { NgModule } from "@angular/core";
import { GridstackItemComponent } from "./gridstack-item.component";
import { GridstackComponent } from "./gridstack.component";
import * as i0 from "@angular/core";
/**
* @deprecated Use GridstackComponent and GridstackItemComponent as standalone components instead.
*
* This NgModule is provided for backward compatibility but is no longer the recommended approach.
* Import components directly in your standalone components or use the new Angular module structure.
*
* @example
* ```typescript
* // Preferred approach - standalone components
* @Component({
* selector: 'my-app',
* imports: [GridstackComponent, GridstackItemComponent],
* template: '<gridstack></gridstack>'
* })
* export class AppComponent {}
*
* // Legacy approach (deprecated)
* @NgModule({
* imports: [GridstackModule]
* })
* export class AppModule {}
* ```
*/
export class GridstackModule {
}
GridstackModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
GridstackModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent], exports: [GridstackItemComponent,
GridstackComponent] });
GridstackModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, decorators: [{
type: NgModule,
args: [{
imports: [
GridstackItemComponent,
GridstackComponent,
],
exports: [
GridstackItemComponent,
GridstackComponent,
],
}]
}] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JpZHN0YWNrLm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2FuZ3VsYXIvcHJvamVjdHMvbGliL3NyYy9saWIvZ3JpZHN0YWNrLm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXpDLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3BFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDOztBQUUzRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQVdILE1BQU0sT0FBTyxlQUFlOzs0R0FBZixlQUFlOzZHQUFmLGVBQWUsWUFSeEIsc0JBQXNCO1FBQ3RCLGtCQUFrQixhQUdsQixzQkFBc0I7UUFDdEIsa0JBQWtCOzZHQUdULGVBQWUsWUFSeEIsc0JBQXNCO1FBQ3RCLGtCQUFrQjsyRkFPVCxlQUFlO2tCQVYzQixRQUFRO21CQUFDO29CQUNSLE9BQU8sRUFBRTt3QkFDUCxzQkFBc0I7d0JBQ3RCLGtCQUFrQjtxQkFDbkI7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLHNCQUFzQjt3QkFDdEIsa0JBQWtCO3FCQUNuQjtpQkFDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxyXG4gKiBncmlkc3RhY2suY29tcG9uZW50LnRzIDEyLjMuM1xyXG4gKiBDb3B5cmlnaHQgKGMpIDIwMjItMjAyNCBBbGFpbiBEdW1lc255IC0gc2VlIEdyaWRTdGFjayByb290IGxpY2Vuc2VcclxuICovXHJcblxyXG5pbXBvcnQgeyBOZ01vZHVsZSB9IGZyb20gXCJAYW5ndWxhci9jb3JlXCI7XHJcblxyXG5pbXBvcnQgeyBHcmlkc3RhY2tJdGVtQ29tcG9uZW50IH0gZnJvbSBcIi4vZ3JpZHN0YWNrLWl0ZW0uY29tcG9uZW50XCI7XHJcbmltcG9ydCB7IEdyaWRzdGFja0NvbXBvbmVudCB9IGZyb20gXCIuL2dyaWRzdGFjay5jb21wb25lbnRcIjtcclxuXHJcbi8qKlxyXG4gKiBAZGVwcmVjYXRlZCBVc2UgR3JpZHN0YWNrQ29tcG9uZW50IGFuZCBHcmlkc3RhY2tJdGVtQ29tcG9uZW50IGFzIHN0YW5kYWxvbmUgY29tcG9uZW50cyBpbnN0ZWFkLlxyXG4gKiBcclxuICogVGhpcyBOZ01vZHVsZSBpcyBwcm92aWRlZCBmb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eSBidXQgaXMgbm8gbG9uZ2VyIHRoZSByZWNvbW1lbmRlZCBhcHByb2FjaC5cclxuICogSW1wb3J0IGNvbXBvbmVudHMgZGlyZWN0bHkgaW4geW91ciBzdGFuZGFsb25lIGNvbXBvbmVudHMgb3IgdXNlIHRoZSBuZXcgQW5ndWxhciBtb2R1bGUgc3RydWN0dXJlLlxyXG4gKiBcclxuICogQGV4YW1wbGVcclxuICogYGBgdHlwZXNjcmlwdFxyXG4gKiAvLyBQcmVmZXJyZWQgYXBwcm9hY2ggLSBzdGFuZGFsb25lIGNvbXBvbmVudHNcclxuICogQENvbXBvbmVudCh7XHJcbiAqICAgc2VsZWN0b3I6ICdteS1hcHAnLFxyXG4gKiAgIGltcG9ydHM6IFtHcmlkc3RhY2tDb21wb25lbnQsIEdyaWRzdGFja0l0ZW1Db21wb25lbnRdLFxyXG4gKiAgIHRlbXBsYXRlOiAnPGdyaWRzdGFjaz48L2dyaWRzdGFjaz4nXHJcbiAqIH0pXHJcbiAqIGV4cG9ydCBjbGFzcyBBcHBDb21wb25lbnQge31cclxuICogXHJcbiAqIC8vIExlZ2FjeSBhcHByb2FjaCAoZGVwcmVjYXRlZClcclxuICogQE5nTW9kdWxlKHtcclxuICogICBpbXBvcnRzOiBbR3JpZHN0YWNrTW9kdWxlXVxyXG4gKiB9KVxyXG4gKiBleHBvcnQgY2xhc3MgQXBwTW9kdWxlIHt9XHJcbiAqIGBgYFxyXG4gKi9cclxuQE5nTW9kdWxlKHtcclxuICBpbXBvcnRzOiBbXHJcbiAgICBHcmlkc3RhY2tJdGVtQ29tcG9uZW50LFxyXG4gICAgR3JpZHN0YWNrQ29tcG9uZW50LFxyXG4gIF0sXHJcbiAgZXhwb3J0czogW1xyXG4gICAgR3JpZHN0YWNrSXRlbUNvbXBvbmVudCxcclxuICAgIEdyaWRzdGFja0NvbXBvbmVudCxcclxuICBdLFxyXG59KVxyXG5leHBvcnQgY2xhc3MgR3JpZHN0YWNrTW9kdWxlIHt9XHJcbiJdfQ==

View File

@@ -1,6 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2025 Alain Dumesny - see GridStack root license
*/
export {};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3Byb2plY3RzL2xpYi9zcmMvbGliL3R5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxyXG4gKiBncmlkc3RhY2staXRlbS5jb21wb25lbnQudHMgMTIuMy4zXHJcbiAqIENvcHlyaWdodCAoYykgMjAyNSBBbGFpbiBEdW1lc255IC0gc2VlIEdyaWRTdGFjayByb290IGxpY2Vuc2VcclxuICovXHJcblxyXG5pbXBvcnQgeyBHcmlkU3RhY2tOb2RlLCBHcmlkU3RhY2tPcHRpb25zLCBHcmlkU3RhY2tXaWRnZXQgfSBmcm9tIFwiZ3JpZHN0YWNrXCI7XHJcblxyXG4vKipcclxuICogRXh0ZW5kZWQgR3JpZFN0YWNrV2lkZ2V0IGludGVyZmFjZSBmb3IgQW5ndWxhciBpbnRlZ3JhdGlvbi5cclxuICogQWRkcyBBbmd1bGFyLXNwZWNpZmljIHByb3BlcnRpZXMgZm9yIGR5bmFtaWMgY29tcG9uZW50IGNyZWF0aW9uLlxyXG4gKi9cclxuZXhwb3J0IGludGVyZmFjZSBOZ0dyaWRTdGFja1dpZGdldCBleHRlbmRzIEdyaWRTdGFja1dpZGdldCB7XHJcbiAgLyoqIEFuZ3VsYXIgY29tcG9uZW50IHNlbGVjdG9yIGZvciBkeW5hbWljIGNyZWF0aW9uIChlLmcuLCAnbXktd2lkZ2V0JykgKi9cclxuICBzZWxlY3Rvcj86IHN0cmluZztcclxuICAvKiogU2VyaWFsaXplZCBkYXRhIGZvciBjb21wb25lbnQgQElucHV0KCkgcHJvcGVydGllcyAqL1xyXG4gIGlucHV0PzogTmdDb21wSW5wdXRzO1xyXG4gIC8qKiBDb25maWd1cmF0aW9uIGZvciBuZXN0ZWQgc3ViLWdyaWRzICovXHJcbiAgc3ViR3JpZE9wdHM/OiBOZ0dyaWRTdGFja09wdGlvbnM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFeHRlbmRlZCBHcmlkU3RhY2tOb2RlIGludGVyZmFjZSBmb3IgQW5ndWxhciBpbnRlZ3JhdGlvbi5cclxuICogQWRkcyBjb21wb25lbnQgc2VsZWN0b3IgZm9yIGR5bmFtaWMgY29udGVudCBjcmVhdGlvbi5cclxuICovXHJcbmV4cG9ydCBpbnRlcmZhY2UgTmdHcmlkU3RhY2tOb2RlIGV4dGVuZHMgR3JpZFN0YWNrTm9kZSB7XHJcbiAgLyoqIEFuZ3VsYXIgY29tcG9uZW50IHNlbGVjdG9yIGZvciB0aGlzIG5vZGUncyBjb250ZW50ICovXHJcbiAgc2VsZWN0b3I/OiBzdHJpbmc7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFeHRlbmRlZCBHcmlkU3RhY2tPcHRpb25zIGludGVyZmFjZSBmb3IgQW5ndWxhciBpbnRlZ3JhdGlvbi5cclxuICogU3VwcG9ydHMgQW5ndWxhci1zcGVjaWZpYyB3aWRnZXQgZGVmaW5pdGlvbnMgYW5kIG5lc3RlZCBncmlkcy5cclxuICovXHJcbmV4cG9ydCBpbnRlcmZhY2UgTmdHcmlkU3RhY2tPcHRpb25zIGV4dGVuZHMgR3JpZFN0YWNrT3B0aW9ucyB7XHJcbiAgLyoqIEFycmF5IG9mIEFuZ3VsYXIgd2lkZ2V0IGRlZmluaXRpb25zIGZvciBpbml0aWFsIGdyaWQgc2V0dXAgKi9cclxuICBjaGlsZHJlbj86IE5nR3JpZFN0YWNrV2lkZ2V0W107XHJcbiAgLyoqIENvbmZpZ3VyYXRpb24gZm9yIG5lc3RlZCBzdWItZ3JpZHMgKEFuZ3VsYXItYXdhcmUpICovXHJcbiAgc3ViR3JpZE9wdHM/OiBOZ0dyaWRTdGFja09wdGlvbnM7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBUeXBlIGZvciBjb21wb25lbnQgaW5wdXQgZGF0YSBzZXJpYWxpemF0aW9uLlxyXG4gKiBNYXBzIEBJbnB1dCgpIHByb3BlcnR5IG5hbWVzIHRvIHRoZWlyIHZhbHVlcyBmb3Igd2lkZ2V0IHBlcnNpc3RlbmNlLlxyXG4gKiBcclxuICogQGV4YW1wbGVcclxuICogYGBgdHlwZXNjcmlwdFxyXG4gKiBjb25zdCBpbnB1dHM6IE5nQ29tcElucHV0cyA9IHtcclxuICogICB0aXRsZTogJ015IFdpZGdldCcsXHJcbiAqICAgdmFsdWU6IDQyLFxyXG4gKiAgIGNvbmZpZzogeyBlbmFibGVkOiB0cnVlIH1cclxuICogfTtcclxuICogYGBgXHJcbiAqL1xyXG5leHBvcnQgdHlwZSBOZ0NvbXBJbnB1dHMgPSB7W2tleTogc3RyaW5nXTogYW55fTtcclxuIl19

View File

@@ -1,647 +0,0 @@
import * as i0 from '@angular/core';
import { Injectable, ViewContainerRef, Component, ViewChild, Input, EventEmitter, reflectComponentType, ContentChildren, Output, NgModule } from '@angular/core';
import { NgIf } from '@angular/common';
import { GridStack } from 'gridstack';
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Base widget class for GridStack Angular integration.
*/
class BaseWidget {
/**
* Override this method to return serializable data for this widget.
*
* Return an object with properties that map to your component's @Input() fields.
* The selector is handled automatically, so only include component-specific data.
*
* @returns Object containing serializable component data
*
* @example
* ```typescript
* serialize() {
* return {
* title: this.title,
* value: this.value,
* settings: this.settings
* };
* }
* ```
*/
serialize() { return; }
/**
* Override this method to handle widget restoration from saved data.
*
* Use this for complex initialization that goes beyond simple @Input() mapping.
* The default implementation automatically assigns input data to component properties.
*
* @param w The saved widget data including input properties
*
* @example
* ```typescript
* deserialize(w: NgGridStackWidget) {
* super.deserialize(w); // Call parent for basic setup
*
* // Custom initialization logic
* if (w.input?.complexData) {
* this.processComplexData(w.input.complexData);
* }
* }
* ```
*/
deserialize(w) {
// save full description for meta data
this.widgetItem = w;
if (!w)
return;
if (w.input)
Object.assign(this, w.input);
}
}
BaseWidget.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
BaseWidget.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, decorators: [{
type: Injectable
}] });
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Angular component wrapper for individual GridStack items.
*
* This component represents a single grid item and handles:
* - Dynamic content creation and management
* - Integration with parent GridStack component
* - Component lifecycle and cleanup
* - Widget options and configuration
*
* Use in combination with GridstackComponent for the parent grid.
*
* @example
* ```html
* <gridstack>
* <gridstack-item [options]="{x: 0, y: 0, w: 2, h: 1}">
* <my-widget-component></my-widget-component>
* </gridstack-item>
* </gridstack>
* ```
*/
class GridstackItemComponent {
constructor(elementRef) {
this.elementRef = elementRef;
this.el._gridItemComp = this;
}
/**
* Grid item configuration options.
* Defines position, size, and behavior of this grid item.
*
* @example
* ```typescript
* itemOptions: GridStackNode = {
* x: 0, y: 0, w: 2, h: 1,
* noResize: true,
* content: 'Item content'
* };
* ```
*/
set options(val) {
var _a;
const grid = (_a = this.el.gridstackNode) === null || _a === void 0 ? void 0 : _a.grid;
if (grid) {
// already built, do an update...
grid.update(this.el, val);
}
else {
// store our custom element in options so we can update it and not re-create a generic div!
this._options = Object.assign(Object.assign({}, val), { el: this.el });
}
}
/** return the latest grid options (from GS once built, otherwise initial values) */
get options() {
return this.el.gridstackNode || this._options || { el: this.el };
}
/** return the native element that contains grid specific fields as well */
get el() { return this.elementRef.nativeElement; }
/** clears the initial options now that we've built */
clearOptions() {
delete this._options;
}
ngOnDestroy() {
this.clearOptions();
delete this.childWidget;
delete this.el._gridItemComp;
delete this.container;
delete this.ref;
}
}
GridstackItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackItemComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
GridstackItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: GridstackItemComponent, isStandalone: true, selector: "gridstack-item", inputs: { options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
<div class="grid-stack-item-content">
<!-- where dynamic items go based on component selector (recommended way), or sub-grids, etc...) -->
<ng-template #container></ng-template>
<!-- any static (defined in DOM - not recommended) content goes here -->
<ng-content></ng-content>
<!-- fallback HTML content from GridStackWidget.content if used instead (not recommended) -->
{{options.content}}
</div>`, isInline: true, styles: [":host{display:block}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackItemComponent, decorators: [{
type: Component,
args: [{ selector: 'gridstack-item', template: `
<div class="grid-stack-item-content">
<!-- where dynamic items go based on component selector (recommended way), or sub-grids, etc...) -->
<ng-template #container></ng-template>
<!-- any static (defined in DOM - not recommended) content goes here -->
<ng-content></ng-content>
<!-- fallback HTML content from GridStackWidget.content if used instead (not recommended) -->
{{options.content}}
</div>`, standalone: true, styles: [":host{display:block}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { container: [{
type: ViewChild,
args: ['container', { read: ViewContainerRef, static: true }]
}], options: [{
type: Input
}] } });
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Angular component wrapper for GridStack.
*
* This component provides Angular integration for GridStack grids, handling:
* - Grid initialization and lifecycle
* - Dynamic component creation and management
* - Event binding and emission
* - Integration with Angular change detection
*
* Use in combination with GridstackItemComponent for individual grid items.
*
* @example
* ```html
* <gridstack [options]="gridOptions" (change)="onGridChange($event)">
* <div empty-content>Drag widgets here</div>
* </gridstack>
* ```
*/
class GridstackComponent {
constructor(elementRef) {
this.elementRef = elementRef;
/**
* GridStack event emitters for Angular integration.
*
* These provide Angular-style event handling for GridStack events.
* Alternatively, use `this.grid.on('event1 event2', callback)` for multiple events.
*
* Note: 'CB' suffix prevents conflicts with native DOM events.
*
* @example
* ```html
* <gridstack (changeCB)="onGridChange($event)" (droppedCB)="onItemDropped($event)">
* </gridstack>
* ```
*/
/** Emitted when widgets are added to the grid */
this.addedCB = new EventEmitter();
/** Emitted when grid layout changes */
this.changeCB = new EventEmitter();
/** Emitted when grid is disabled */
this.disableCB = new EventEmitter();
/** Emitted during widget drag operations */
this.dragCB = new EventEmitter();
/** Emitted when widget drag starts */
this.dragStartCB = new EventEmitter();
/** Emitted when widget drag stops */
this.dragStopCB = new EventEmitter();
/** Emitted when widget is dropped */
this.droppedCB = new EventEmitter();
/** Emitted when grid is enabled */
this.enableCB = new EventEmitter();
/** Emitted when widgets are removed from the grid */
this.removedCB = new EventEmitter();
/** Emitted during widget resize operations */
this.resizeCB = new EventEmitter();
/** Emitted when widget resize starts */
this.resizeStartCB = new EventEmitter();
/** Emitted when widget resize stops */
this.resizeStopCB = new EventEmitter();
// set globally our method to create the right widget type
if (!GridStack.addRemoveCB) {
GridStack.addRemoveCB = gsCreateNgComponents;
}
if (!GridStack.saveCB) {
GridStack.saveCB = gsSaveAdditionalNgInfo;
}
if (!GridStack.updateCB) {
GridStack.updateCB = gsUpdateNgComponents;
}
this.el._gridComp = this;
}
/**
* Grid configuration options.
* Can be set before grid initialization or updated after grid is created.
*
* @example
* ```typescript
* gridOptions: GridStackOptions = {
* column: 12,
* cellHeight: 'auto',
* animate: true
* };
* ```
*/
set options(o) {
if (this._grid) {
this._grid.updateOptions(o);
}
else {
this._options = o;
}
}
/** Get the current running grid options */
get options() { var _a; return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.opts) || this._options || {}; }
/**
* Get the native DOM element that contains grid-specific fields.
* This element has GridStack properties attached to it.
*/
get el() { return this.elementRef.nativeElement; }
/**
* Get the underlying GridStack instance.
* Use this to access GridStack API methods directly.
*
* @example
* ```typescript
* this.gridComponent.grid.addWidget({x: 0, y: 0, w: 2, h: 1});
* ```
*/
get grid() { return this._grid; }
/**
* Register a list of Angular components for dynamic creation.
*
* @param typeList Array of component types to register
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([
* MyWidgetComponent,
* AnotherWidgetComponent
* ]);
* ```
*/
static addComponentToSelectorType(typeList) {
typeList.forEach(type => GridstackComponent.selectorToType[GridstackComponent.getSelector(type)] = type);
}
/**
* Extract the selector string from an Angular component type.
*
* @param type The component type to get selector from
* @returns The component's selector string
*/
static getSelector(type) {
return reflectComponentType(type).selector;
}
ngOnInit() {
var _a, _b;
// init ourself before any template children are created since we track them below anyway - no need to double create+update widgets
this.loaded = !!((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b.length);
this._grid = GridStack.init(this._options, this.el);
delete this._options; // GS has it now
this.checkEmpty();
}
/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
ngAfterContentInit() {
var _a;
// track whenever the children list changes and update the layout...
this._sub = (_a = this.gridstackItems) === null || _a === void 0 ? void 0 : _a.changes.subscribe(() => this.updateAll());
// ...and do this once at least unless we loaded children already
if (!this.loaded)
this.updateAll();
this.hookEvents(this.grid);
}
ngOnDestroy() {
var _a, _b;
this.unhookEvents(this._grid);
(_a = this._sub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
(_b = this._grid) === null || _b === void 0 ? void 0 : _b.destroy();
delete this._grid;
delete this.el._gridComp;
delete this.container;
delete this.ref;
}
/**
* called when the TEMPLATE (not recommended) list of items changes - get a list of nodes and
* update the layout accordingly (which will take care of adding/removing items changed by Angular)
*/
updateAll() {
var _a;
if (!this.grid)
return;
const layout = [];
(_a = this.gridstackItems) === null || _a === void 0 ? void 0 : _a.forEach(item => {
layout.push(item.options);
item.clearOptions();
});
this.grid.load(layout); // efficient that does diffs only
}
/** check if the grid is empty, if so show alternative content */
checkEmpty() {
if (!this.grid)
return;
this.isEmpty = !this.grid.engine.nodes.length;
}
/** get all known events as easy to use Outputs for convenience */
hookEvents(grid) {
if (!grid)
return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode)
return;
grid
.on('added', (event, nodes) => {
var _a;
const gridComp = ((_a = nodes[0].grid) === null || _a === void 0 ? void 0 : _a.el._gridComp) || this;
gridComp.checkEmpty();
this.addedCB.emit({ event, nodes });
})
.on('change', (event, nodes) => this.changeCB.emit({ event, nodes }))
.on('disable', (event) => this.disableCB.emit({ event }))
.on('drag', (event, el) => this.dragCB.emit({ event, el }))
.on('dragstart', (event, el) => this.dragStartCB.emit({ event, el }))
.on('dragstop', (event, el) => this.dragStopCB.emit({ event, el }))
.on('dropped', (event, previousNode, newNode) => this.droppedCB.emit({ event, previousNode, newNode }))
.on('enable', (event) => this.enableCB.emit({ event }))
.on('removed', (event, nodes) => {
var _a;
const gridComp = ((_a = nodes[0].grid) === null || _a === void 0 ? void 0 : _a.el._gridComp) || this;
gridComp.checkEmpty();
this.removedCB.emit({ event, nodes });
})
.on('resize', (event, el) => this.resizeCB.emit({ event, el }))
.on('resizestart', (event, el) => this.resizeStartCB.emit({ event, el }))
.on('resizestop', (event, el) => this.resizeStopCB.emit({ event, el }));
}
unhookEvents(grid) {
if (!grid)
return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode)
return;
grid.off('added change disable drag dragstart dragstop dropped enable removed resize resizestart resizestop');
}
}
/**
* Mapping of component selectors to their types for dynamic creation.
*
* This enables dynamic component instantiation from string selectors.
* Angular doesn't provide public access to this mapping, so we maintain our own.
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([MyWidgetComponent]);
* ```
*/
GridstackComponent.selectorToType = {};
GridstackComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
GridstackComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: GridstackComponent, isStandalone: true, selector: "gridstack", inputs: { options: "options", isEmpty: "isEmpty" }, outputs: { addedCB: "addedCB", changeCB: "changeCB", disableCB: "disableCB", dragCB: "dragCB", dragStartCB: "dragStartCB", dragStopCB: "dragStopCB", droppedCB: "droppedCB", enableCB: "enableCB", removedCB: "removedCB", resizeCB: "resizeCB", resizeStartCB: "resizeStartCB", resizeStopCB: "resizeStopCB" }, queries: [{ propertyName: "gridstackItems", predicate: GridstackItemComponent }], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
<!-- content to show when when grid is empty, like instructions on how to add widgets -->
<ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
<!-- where dynamic items go -->
<ng-template #container></ng-template>
<!-- where template items go -->
<ng-content></ng-content>
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackComponent, decorators: [{
type: Component,
args: [{ selector: 'gridstack', template: `
<!-- content to show when when grid is empty, like instructions on how to add widgets -->
<ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
<!-- where dynamic items go -->
<ng-template #container></ng-template>
<!-- where template items go -->
<ng-content></ng-content>
`, standalone: true, imports: [NgIf], styles: [":host{display:block}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { gridstackItems: [{
type: ContentChildren,
args: [GridstackItemComponent]
}], container: [{
type: ViewChild,
args: ['container', { read: ViewContainerRef, static: true }]
}], options: [{
type: Input
}], isEmpty: [{
type: Input
}], addedCB: [{
type: Output
}], changeCB: [{
type: Output
}], disableCB: [{
type: Output
}], dragCB: [{
type: Output
}], dragStartCB: [{
type: Output
}], dragStopCB: [{
type: Output
}], droppedCB: [{
type: Output
}], enableCB: [{
type: Output
}], removedCB: [{
type: Output
}], resizeCB: [{
type: Output
}], resizeStartCB: [{
type: Output
}], resizeStopCB: [{
type: Output
}] } });
/**
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
**/
function gsCreateNgComponents(host, n, add, isGrid) {
var _a, _b, _c, _d, _e, _f, _g;
if (add) {
//
// create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
//
if (!host)
return;
if (isGrid) {
// TODO: figure out how to create ng component inside regular Div. need to access app injectors...
// if (!container) {
// const hostElement: Element = host;
// const environmentInjector: EnvironmentInjector;
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
// }
const gridItemComp = (_a = host.parentElement) === null || _a === void 0 ? void 0 : _a._gridItemComp;
if (!gridItemComp)
return;
// check if gridItem has a child component with 'container' exposed to create under..
const container = ((_b = gridItemComp.childWidget) === null || _b === void 0 ? void 0 : _b.container) || gridItemComp.container;
const gridRef = container === null || container === void 0 ? void 0 : container.createComponent(GridstackComponent);
const grid = gridRef === null || gridRef === void 0 ? void 0 : gridRef.instance;
if (!grid)
return;
grid.ref = gridRef;
grid.options = n;
return grid.el;
}
else {
const gridComp = host._gridComp;
const gridItemRef = (_c = gridComp === null || gridComp === void 0 ? void 0 : gridComp.container) === null || _c === void 0 ? void 0 : _c.createComponent(GridstackItemComponent);
const gridItem = gridItemRef === null || gridItemRef === void 0 ? void 0 : gridItemRef.instance;
if (!gridItem)
return;
gridItem.ref = gridItemRef;
// define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
const selector = n.selector;
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
if (type) {
// shared code to create our selector component
const createComp = () => {
var _a, _b;
const childWidget = (_b = (_a = gridItem.container) === null || _a === void 0 ? void 0 : _a.createComponent(type)) === null || _b === void 0 ? void 0 : _b.instance;
// if proper BaseWidget subclass, save it and load additional data
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
gridItem.childWidget = childWidget;
childWidget.deserialize(n);
}
};
const lazyLoad = n.lazyLoad || ((_e = (_d = n.grid) === null || _d === void 0 ? void 0 : _d.opts) === null || _e === void 0 ? void 0 : _e.lazyLoad) && n.lazyLoad !== false;
if (lazyLoad) {
if (!n.visibleObservable) {
n.visibleObservable = new IntersectionObserver(([entry]) => {
var _a;
if (entry.isIntersecting) {
(_a = n.visibleObservable) === null || _a === void 0 ? void 0 : _a.disconnect();
delete n.visibleObservable;
createComp();
}
});
window.setTimeout(() => { var _a; return (_a = n.visibleObservable) === null || _a === void 0 ? void 0 : _a.observe(gridItem.el); }); // wait until callee sets position attributes
}
}
else
createComp();
}
return gridItem.el;
}
}
else {
//
// REMOVE - have to call ComponentRef:destroy() for dynamic objects to correctly remove themselves
// Note: this will destroy all children dynamic components as well: gridItem -> childWidget
//
if (isGrid) {
const grid = (_f = n.el) === null || _f === void 0 ? void 0 : _f._gridComp;
if (grid === null || grid === void 0 ? void 0 : grid.ref)
grid.ref.destroy();
else
grid === null || grid === void 0 ? void 0 : grid.ngOnDestroy();
}
else {
const gridItem = (_g = n.el) === null || _g === void 0 ? void 0 : _g._gridItemComp;
if (gridItem === null || gridItem === void 0 ? void 0 : gridItem.ref)
gridItem.ref.destroy();
else
gridItem === null || gridItem === void 0 ? void 0 : gridItem.ngOnDestroy();
}
}
return;
}
/**
* called for each item in the grid - check if additional information needs to be saved.
* Note: since this is options minus gridstack protected members using Utils.removeInternalForSave(),
* this typically doesn't need to do anything. However your custom Component @Input() are now supported
* using BaseWidget.serialize()
*/
function gsSaveAdditionalNgInfo(n, w) {
var _a, _b, _c;
const gridItem = (_a = n.el) === null || _a === void 0 ? void 0 : _a._gridItemComp;
if (gridItem) {
const input = (_b = gridItem.childWidget) === null || _b === void 0 ? void 0 : _b.serialize();
if (input) {
w.input = input;
}
return;
}
// else check if Grid
const grid = (_c = n.el) === null || _c === void 0 ? void 0 : _c._gridComp;
if (grid) {
//.... save any custom data
}
}
/**
* track when widgeta re updated (rather than created) to make sure we de-serialize them as well
*/
function gsUpdateNgComponents(n) {
var _a;
const w = n;
const gridItem = (_a = n.el) === null || _a === void 0 ? void 0 : _a._gridItemComp;
if ((gridItem === null || gridItem === void 0 ? void 0 : gridItem.childWidget) && w.input)
gridItem.childWidget.deserialize(w);
}
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* @deprecated Use GridstackComponent and GridstackItemComponent as standalone components instead.
*
* This NgModule is provided for backward compatibility but is no longer the recommended approach.
* Import components directly in your standalone components or use the new Angular module structure.
*
* @example
* ```typescript
* // Preferred approach - standalone components
* @Component({
* selector: 'my-app',
* imports: [GridstackComponent, GridstackItemComponent],
* template: '<gridstack></gridstack>'
* })
* export class AppComponent {}
*
* // Legacy approach (deprecated)
* @NgModule({
* imports: [GridstackModule]
* })
* export class AppModule {}
* ```
*/
class GridstackModule {
}
GridstackModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
GridstackModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent], exports: [GridstackItemComponent,
GridstackComponent] });
GridstackModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, decorators: [{
type: NgModule,
args: [{
imports: [
GridstackItemComponent,
GridstackComponent,
],
exports: [
GridstackItemComponent,
GridstackComponent,
],
}]
}] });
/*
* Public API Surface of gridstack-angular
*/
/**
* Generated bundle index. Do not edit.
*/
export { BaseWidget, GridstackComponent, GridstackItemComponent, GridstackModule, gsCreateNgComponents, gsSaveAdditionalNgInfo, gsUpdateNgComponents };
//# sourceMappingURL=gridstack-angular.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,640 +0,0 @@
import * as i0 from '@angular/core';
import { Injectable, ViewContainerRef, Component, ViewChild, Input, EventEmitter, reflectComponentType, ContentChildren, Output, NgModule } from '@angular/core';
import { NgIf } from '@angular/common';
import { GridStack } from 'gridstack';
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2025 Alain Dumesny - see GridStack root license
*/
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Base widget class for GridStack Angular integration.
*/
class BaseWidget {
/**
* Override this method to return serializable data for this widget.
*
* Return an object with properties that map to your component's @Input() fields.
* The selector is handled automatically, so only include component-specific data.
*
* @returns Object containing serializable component data
*
* @example
* ```typescript
* serialize() {
* return {
* title: this.title,
* value: this.value,
* settings: this.settings
* };
* }
* ```
*/
serialize() { return; }
/**
* Override this method to handle widget restoration from saved data.
*
* Use this for complex initialization that goes beyond simple @Input() mapping.
* The default implementation automatically assigns input data to component properties.
*
* @param w The saved widget data including input properties
*
* @example
* ```typescript
* deserialize(w: NgGridStackWidget) {
* super.deserialize(w); // Call parent for basic setup
*
* // Custom initialization logic
* if (w.input?.complexData) {
* this.processComplexData(w.input.complexData);
* }
* }
* ```
*/
deserialize(w) {
// save full description for meta data
this.widgetItem = w;
if (!w)
return;
if (w.input)
Object.assign(this, w.input);
}
}
BaseWidget.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
BaseWidget.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseWidget, decorators: [{
type: Injectable
}] });
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Angular component wrapper for individual GridStack items.
*
* This component represents a single grid item and handles:
* - Dynamic content creation and management
* - Integration with parent GridStack component
* - Component lifecycle and cleanup
* - Widget options and configuration
*
* Use in combination with GridstackComponent for the parent grid.
*
* @example
* ```html
* <gridstack>
* <gridstack-item [options]="{x: 0, y: 0, w: 2, h: 1}">
* <my-widget-component></my-widget-component>
* </gridstack-item>
* </gridstack>
* ```
*/
class GridstackItemComponent {
constructor(elementRef) {
this.elementRef = elementRef;
this.el._gridItemComp = this;
}
/**
* Grid item configuration options.
* Defines position, size, and behavior of this grid item.
*
* @example
* ```typescript
* itemOptions: GridStackNode = {
* x: 0, y: 0, w: 2, h: 1,
* noResize: true,
* content: 'Item content'
* };
* ```
*/
set options(val) {
const grid = this.el.gridstackNode?.grid;
if (grid) {
// already built, do an update...
grid.update(this.el, val);
}
else {
// store our custom element in options so we can update it and not re-create a generic div!
this._options = { ...val, el: this.el };
}
}
/** return the latest grid options (from GS once built, otherwise initial values) */
get options() {
return this.el.gridstackNode || this._options || { el: this.el };
}
/** return the native element that contains grid specific fields as well */
get el() { return this.elementRef.nativeElement; }
/** clears the initial options now that we've built */
clearOptions() {
delete this._options;
}
ngOnDestroy() {
this.clearOptions();
delete this.childWidget;
delete this.el._gridItemComp;
delete this.container;
delete this.ref;
}
}
GridstackItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackItemComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
GridstackItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: GridstackItemComponent, isStandalone: true, selector: "gridstack-item", inputs: { options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
<div class="grid-stack-item-content">
<!-- where dynamic items go based on component selector (recommended way), or sub-grids, etc...) -->
<ng-template #container></ng-template>
<!-- any static (defined in DOM - not recommended) content goes here -->
<ng-content></ng-content>
<!-- fallback HTML content from GridStackWidget.content if used instead (not recommended) -->
{{options.content}}
</div>`, isInline: true, styles: [":host{display:block}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackItemComponent, decorators: [{
type: Component,
args: [{ selector: 'gridstack-item', template: `
<div class="grid-stack-item-content">
<!-- where dynamic items go based on component selector (recommended way), or sub-grids, etc...) -->
<ng-template #container></ng-template>
<!-- any static (defined in DOM - not recommended) content goes here -->
<ng-content></ng-content>
<!-- fallback HTML content from GridStackWidget.content if used instead (not recommended) -->
{{options.content}}
</div>`, standalone: true, styles: [":host{display:block}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { container: [{
type: ViewChild,
args: ['container', { read: ViewContainerRef, static: true }]
}], options: [{
type: Input
}] } });
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Angular component wrapper for GridStack.
*
* This component provides Angular integration for GridStack grids, handling:
* - Grid initialization and lifecycle
* - Dynamic component creation and management
* - Event binding and emission
* - Integration with Angular change detection
*
* Use in combination with GridstackItemComponent for individual grid items.
*
* @example
* ```html
* <gridstack [options]="gridOptions" (change)="onGridChange($event)">
* <div empty-content>Drag widgets here</div>
* </gridstack>
* ```
*/
class GridstackComponent {
constructor(elementRef) {
this.elementRef = elementRef;
/**
* GridStack event emitters for Angular integration.
*
* These provide Angular-style event handling for GridStack events.
* Alternatively, use `this.grid.on('event1 event2', callback)` for multiple events.
*
* Note: 'CB' suffix prevents conflicts with native DOM events.
*
* @example
* ```html
* <gridstack (changeCB)="onGridChange($event)" (droppedCB)="onItemDropped($event)">
* </gridstack>
* ```
*/
/** Emitted when widgets are added to the grid */
this.addedCB = new EventEmitter();
/** Emitted when grid layout changes */
this.changeCB = new EventEmitter();
/** Emitted when grid is disabled */
this.disableCB = new EventEmitter();
/** Emitted during widget drag operations */
this.dragCB = new EventEmitter();
/** Emitted when widget drag starts */
this.dragStartCB = new EventEmitter();
/** Emitted when widget drag stops */
this.dragStopCB = new EventEmitter();
/** Emitted when widget is dropped */
this.droppedCB = new EventEmitter();
/** Emitted when grid is enabled */
this.enableCB = new EventEmitter();
/** Emitted when widgets are removed from the grid */
this.removedCB = new EventEmitter();
/** Emitted during widget resize operations */
this.resizeCB = new EventEmitter();
/** Emitted when widget resize starts */
this.resizeStartCB = new EventEmitter();
/** Emitted when widget resize stops */
this.resizeStopCB = new EventEmitter();
// set globally our method to create the right widget type
if (!GridStack.addRemoveCB) {
GridStack.addRemoveCB = gsCreateNgComponents;
}
if (!GridStack.saveCB) {
GridStack.saveCB = gsSaveAdditionalNgInfo;
}
if (!GridStack.updateCB) {
GridStack.updateCB = gsUpdateNgComponents;
}
this.el._gridComp = this;
}
/**
* Grid configuration options.
* Can be set before grid initialization or updated after grid is created.
*
* @example
* ```typescript
* gridOptions: GridStackOptions = {
* column: 12,
* cellHeight: 'auto',
* animate: true
* };
* ```
*/
set options(o) {
if (this._grid) {
this._grid.updateOptions(o);
}
else {
this._options = o;
}
}
/** Get the current running grid options */
get options() { return this._grid?.opts || this._options || {}; }
/**
* Get the native DOM element that contains grid-specific fields.
* This element has GridStack properties attached to it.
*/
get el() { return this.elementRef.nativeElement; }
/**
* Get the underlying GridStack instance.
* Use this to access GridStack API methods directly.
*
* @example
* ```typescript
* this.gridComponent.grid.addWidget({x: 0, y: 0, w: 2, h: 1});
* ```
*/
get grid() { return this._grid; }
/**
* Register a list of Angular components for dynamic creation.
*
* @param typeList Array of component types to register
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([
* MyWidgetComponent,
* AnotherWidgetComponent
* ]);
* ```
*/
static addComponentToSelectorType(typeList) {
typeList.forEach(type => GridstackComponent.selectorToType[GridstackComponent.getSelector(type)] = type);
}
/**
* Extract the selector string from an Angular component type.
*
* @param type The component type to get selector from
* @returns The component's selector string
*/
static getSelector(type) {
return reflectComponentType(type).selector;
}
ngOnInit() {
// init ourself before any template children are created since we track them below anyway - no need to double create+update widgets
this.loaded = !!this.options?.children?.length;
this._grid = GridStack.init(this._options, this.el);
delete this._options; // GS has it now
this.checkEmpty();
}
/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
ngAfterContentInit() {
// track whenever the children list changes and update the layout...
this._sub = this.gridstackItems?.changes.subscribe(() => this.updateAll());
// ...and do this once at least unless we loaded children already
if (!this.loaded)
this.updateAll();
this.hookEvents(this.grid);
}
ngOnDestroy() {
this.unhookEvents(this._grid);
this._sub?.unsubscribe();
this._grid?.destroy();
delete this._grid;
delete this.el._gridComp;
delete this.container;
delete this.ref;
}
/**
* called when the TEMPLATE (not recommended) list of items changes - get a list of nodes and
* update the layout accordingly (which will take care of adding/removing items changed by Angular)
*/
updateAll() {
if (!this.grid)
return;
const layout = [];
this.gridstackItems?.forEach(item => {
layout.push(item.options);
item.clearOptions();
});
this.grid.load(layout); // efficient that does diffs only
}
/** check if the grid is empty, if so show alternative content */
checkEmpty() {
if (!this.grid)
return;
this.isEmpty = !this.grid.engine.nodes.length;
}
/** get all known events as easy to use Outputs for convenience */
hookEvents(grid) {
if (!grid)
return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode)
return;
grid
.on('added', (event, nodes) => {
const gridComp = nodes[0].grid?.el._gridComp || this;
gridComp.checkEmpty();
this.addedCB.emit({ event, nodes });
})
.on('change', (event, nodes) => this.changeCB.emit({ event, nodes }))
.on('disable', (event) => this.disableCB.emit({ event }))
.on('drag', (event, el) => this.dragCB.emit({ event, el }))
.on('dragstart', (event, el) => this.dragStartCB.emit({ event, el }))
.on('dragstop', (event, el) => this.dragStopCB.emit({ event, el }))
.on('dropped', (event, previousNode, newNode) => this.droppedCB.emit({ event, previousNode, newNode }))
.on('enable', (event) => this.enableCB.emit({ event }))
.on('removed', (event, nodes) => {
const gridComp = nodes[0].grid?.el._gridComp || this;
gridComp.checkEmpty();
this.removedCB.emit({ event, nodes });
})
.on('resize', (event, el) => this.resizeCB.emit({ event, el }))
.on('resizestart', (event, el) => this.resizeStartCB.emit({ event, el }))
.on('resizestop', (event, el) => this.resizeStopCB.emit({ event, el }));
}
unhookEvents(grid) {
if (!grid)
return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode)
return;
grid.off('added change disable drag dragstart dragstop dropped enable removed resize resizestart resizestop');
}
}
/**
* Mapping of component selectors to their types for dynamic creation.
*
* This enables dynamic component instantiation from string selectors.
* Angular doesn't provide public access to this mapping, so we maintain our own.
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([MyWidgetComponent]);
* ```
*/
GridstackComponent.selectorToType = {};
GridstackComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
GridstackComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: GridstackComponent, isStandalone: true, selector: "gridstack", inputs: { options: "options", isEmpty: "isEmpty" }, outputs: { addedCB: "addedCB", changeCB: "changeCB", disableCB: "disableCB", dragCB: "dragCB", dragStartCB: "dragStartCB", dragStopCB: "dragStopCB", droppedCB: "droppedCB", enableCB: "enableCB", removedCB: "removedCB", resizeCB: "resizeCB", resizeStartCB: "resizeStartCB", resizeStopCB: "resizeStopCB" }, queries: [{ propertyName: "gridstackItems", predicate: GridstackItemComponent }], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
<!-- content to show when when grid is empty, like instructions on how to add widgets -->
<ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
<!-- where dynamic items go -->
<ng-template #container></ng-template>
<!-- where template items go -->
<ng-content></ng-content>
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackComponent, decorators: [{
type: Component,
args: [{ selector: 'gridstack', template: `
<!-- content to show when when grid is empty, like instructions on how to add widgets -->
<ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
<!-- where dynamic items go -->
<ng-template #container></ng-template>
<!-- where template items go -->
<ng-content></ng-content>
`, standalone: true, imports: [NgIf], styles: [":host{display:block}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { gridstackItems: [{
type: ContentChildren,
args: [GridstackItemComponent]
}], container: [{
type: ViewChild,
args: ['container', { read: ViewContainerRef, static: true }]
}], options: [{
type: Input
}], isEmpty: [{
type: Input
}], addedCB: [{
type: Output
}], changeCB: [{
type: Output
}], disableCB: [{
type: Output
}], dragCB: [{
type: Output
}], dragStartCB: [{
type: Output
}], dragStopCB: [{
type: Output
}], droppedCB: [{
type: Output
}], enableCB: [{
type: Output
}], removedCB: [{
type: Output
}], resizeCB: [{
type: Output
}], resizeStartCB: [{
type: Output
}], resizeStopCB: [{
type: Output
}] } });
/**
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
**/
function gsCreateNgComponents(host, n, add, isGrid) {
if (add) {
//
// create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
//
if (!host)
return;
if (isGrid) {
// TODO: figure out how to create ng component inside regular Div. need to access app injectors...
// if (!container) {
// const hostElement: Element = host;
// const environmentInjector: EnvironmentInjector;
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
// }
const gridItemComp = host.parentElement?._gridItemComp;
if (!gridItemComp)
return;
// check if gridItem has a child component with 'container' exposed to create under..
const container = gridItemComp.childWidget?.container || gridItemComp.container;
const gridRef = container?.createComponent(GridstackComponent);
const grid = gridRef?.instance;
if (!grid)
return;
grid.ref = gridRef;
grid.options = n;
return grid.el;
}
else {
const gridComp = host._gridComp;
const gridItemRef = gridComp?.container?.createComponent(GridstackItemComponent);
const gridItem = gridItemRef?.instance;
if (!gridItem)
return;
gridItem.ref = gridItemRef;
// define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
const selector = n.selector;
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
if (type) {
// shared code to create our selector component
const createComp = () => {
const childWidget = gridItem.container?.createComponent(type)?.instance;
// if proper BaseWidget subclass, save it and load additional data
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
gridItem.childWidget = childWidget;
childWidget.deserialize(n);
}
};
const lazyLoad = n.lazyLoad || n.grid?.opts?.lazyLoad && n.lazyLoad !== false;
if (lazyLoad) {
if (!n.visibleObservable) {
n.visibleObservable = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
n.visibleObservable?.disconnect();
delete n.visibleObservable;
createComp();
}
});
window.setTimeout(() => n.visibleObservable?.observe(gridItem.el)); // wait until callee sets position attributes
}
}
else
createComp();
}
return gridItem.el;
}
}
else {
//
// REMOVE - have to call ComponentRef:destroy() for dynamic objects to correctly remove themselves
// Note: this will destroy all children dynamic components as well: gridItem -> childWidget
//
if (isGrid) {
const grid = n.el?._gridComp;
if (grid?.ref)
grid.ref.destroy();
else
grid?.ngOnDestroy();
}
else {
const gridItem = n.el?._gridItemComp;
if (gridItem?.ref)
gridItem.ref.destroy();
else
gridItem?.ngOnDestroy();
}
}
return;
}
/**
* called for each item in the grid - check if additional information needs to be saved.
* Note: since this is options minus gridstack protected members using Utils.removeInternalForSave(),
* this typically doesn't need to do anything. However your custom Component @Input() are now supported
* using BaseWidget.serialize()
*/
function gsSaveAdditionalNgInfo(n, w) {
const gridItem = n.el?._gridItemComp;
if (gridItem) {
const input = gridItem.childWidget?.serialize();
if (input) {
w.input = input;
}
return;
}
// else check if Grid
const grid = n.el?._gridComp;
if (grid) {
//.... save any custom data
}
}
/**
* track when widgeta re updated (rather than created) to make sure we de-serialize them as well
*/
function gsUpdateNgComponents(n) {
const w = n;
const gridItem = n.el?._gridItemComp;
if (gridItem?.childWidget && w.input)
gridItem.childWidget.deserialize(w);
}
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* @deprecated Use GridstackComponent and GridstackItemComponent as standalone components instead.
*
* This NgModule is provided for backward compatibility but is no longer the recommended approach.
* Import components directly in your standalone components or use the new Angular module structure.
*
* @example
* ```typescript
* // Preferred approach - standalone components
* @Component({
* selector: 'my-app',
* imports: [GridstackComponent, GridstackItemComponent],
* template: '<gridstack></gridstack>'
* })
* export class AppComponent {}
*
* // Legacy approach (deprecated)
* @NgModule({
* imports: [GridstackModule]
* })
* export class AppModule {}
* ```
*/
class GridstackModule {
}
GridstackModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
GridstackModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent], exports: [GridstackItemComponent,
GridstackComponent] });
GridstackModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, imports: [GridstackItemComponent,
GridstackComponent] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GridstackModule, decorators: [{
type: NgModule,
args: [{
imports: [
GridstackItemComponent,
GridstackComponent,
],
exports: [
GridstackItemComponent,
GridstackComponent,
],
}]
}] });
/*
* Public API Surface of gridstack-angular
*/
/**
* Generated bundle index. Do not edit.
*/
export { BaseWidget, GridstackComponent, GridstackItemComponent, GridstackModule, gsCreateNgComponents, gsSaveAdditionalNgInfo, gsUpdateNgComponents };
//# sourceMappingURL=gridstack-angular.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
export * from './lib/types';
export * from './lib/base-widget';
export * from './lib/gridstack-item.component';
export * from './lib/gridstack.component';
export * from './lib/gridstack.module';

View File

@@ -1,55 +0,0 @@
import { NgCompInputs, NgGridStackWidget } from './types';
import * as i0 from "@angular/core";
/**
* Base widget class for GridStack Angular integration.
*/
export declare abstract class BaseWidget {
/**
* Complete widget definition including position, size, and Angular-specific data.
* Populated automatically when the widget is loaded or saved.
*/
widgetItem?: NgGridStackWidget;
/**
* Override this method to return serializable data for this widget.
*
* Return an object with properties that map to your component's @Input() fields.
* The selector is handled automatically, so only include component-specific data.
*
* @returns Object containing serializable component data
*
* @example
* ```typescript
* serialize() {
* return {
* title: this.title,
* value: this.value,
* settings: this.settings
* };
* }
* ```
*/
serialize(): NgCompInputs | undefined;
/**
* Override this method to handle widget restoration from saved data.
*
* Use this for complex initialization that goes beyond simple @Input() mapping.
* The default implementation automatically assigns input data to component properties.
*
* @param w The saved widget data including input properties
*
* @example
* ```typescript
* deserialize(w: NgGridStackWidget) {
* super.deserialize(w); // Call parent for basic setup
*
* // Custom initialization logic
* if (w.input?.complexData) {
* this.processComplexData(w.input.complexData);
* }
* }
* ```
*/
deserialize(w: NgGridStackWidget): void;
static ɵfac: i0.ɵɵFactoryDeclaration<BaseWidget, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<BaseWidget>;
}

View File

@@ -1,79 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import { ElementRef, ViewContainerRef, OnDestroy, ComponentRef } from '@angular/core';
import { GridItemHTMLElement, GridStackNode } from 'gridstack';
import { BaseWidget } from './base-widget';
import * as i0 from "@angular/core";
/**
* Extended HTMLElement interface for grid items.
* Stores a back-reference to the Angular component for integration.
*/
export interface GridItemCompHTMLElement extends GridItemHTMLElement {
/** Back-reference to the Angular GridStackItem component */
_gridItemComp?: GridstackItemComponent;
}
/**
* Angular component wrapper for individual GridStack items.
*
* This component represents a single grid item and handles:
* - Dynamic content creation and management
* - Integration with parent GridStack component
* - Component lifecycle and cleanup
* - Widget options and configuration
*
* Use in combination with GridstackComponent for the parent grid.
*
* @example
* ```html
* <gridstack>
* <gridstack-item [options]="{x: 0, y: 0, w: 2, h: 1}">
* <my-widget-component></my-widget-component>
* </gridstack-item>
* </gridstack>
* ```
*/
export declare class GridstackItemComponent implements OnDestroy {
protected readonly elementRef: ElementRef<GridItemCompHTMLElement>;
/**
* Container for dynamic component creation within this grid item.
* Used to append child components programmatically.
*/
container?: ViewContainerRef;
/**
* Component reference for dynamic component removal.
* Used internally when this component is created dynamically.
*/
ref: ComponentRef<GridstackItemComponent> | undefined;
/**
* Reference to child widget component for serialization.
* Used to save/restore additional data along with grid position.
*/
childWidget: BaseWidget | undefined;
/**
* Grid item configuration options.
* Defines position, size, and behavior of this grid item.
*
* @example
* ```typescript
* itemOptions: GridStackNode = {
* x: 0, y: 0, w: 2, h: 1,
* noResize: true,
* content: 'Item content'
* };
* ```
*/
set options(val: GridStackNode);
/** return the latest grid options (from GS once built, otherwise initial values) */
get options(): GridStackNode;
protected _options?: GridStackNode;
/** return the native element that contains grid specific fields as well */
get el(): GridItemCompHTMLElement;
/** clears the initial options now that we've built */
clearOptions(): void;
constructor(elementRef: ElementRef<GridItemCompHTMLElement>);
ngOnDestroy(): void;
static ɵfac: i0.ɵɵFactoryDeclaration<GridstackItemComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<GridstackItemComponent, "gridstack-item", never, { "options": "options"; }, {}, never, ["*"], true>;
}

View File

@@ -1,236 +0,0 @@
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import { AfterContentInit, ElementRef, EventEmitter, OnDestroy, OnInit, QueryList, Type, ViewContainerRef, ComponentRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { GridHTMLElement, GridItemHTMLElement, GridStack, GridStackNode, GridStackOptions } from 'gridstack';
import { NgGridStackNode, NgGridStackWidget } from './types';
import { GridstackItemComponent } from './gridstack-item.component';
import * as i0 from "@angular/core";
/**
* Event handler callback signatures for different GridStack events.
* These types define the structure of data passed to Angular event emitters.
*/
/** Callback for general events (enable, disable, etc.) */
export declare type eventCB = {
event: Event;
};
/** Callback for element-specific events (resize, drag, etc.) */
export declare type elementCB = {
event: Event;
el: GridItemHTMLElement;
};
/** Callback for events affecting multiple nodes (change, etc.) */
export declare type nodesCB = {
event: Event;
nodes: GridStackNode[];
};
/** Callback for drop events with before/after node state */
export declare type droppedCB = {
event: Event;
previousNode: GridStackNode;
newNode: GridStackNode;
};
/**
* Extended HTMLElement interface for the grid container.
* Stores a back-reference to the Angular component for integration purposes.
*/
export interface GridCompHTMLElement extends GridHTMLElement {
/** Back-reference to the Angular GridStack component */
_gridComp?: GridstackComponent;
}
/**
* Mapping of selector strings to Angular component types.
* Used for dynamic component creation based on widget selectors.
*/
export declare type SelectorToType = {
[key: string]: Type<Object>;
};
/**
* Angular component wrapper for GridStack.
*
* This component provides Angular integration for GridStack grids, handling:
* - Grid initialization and lifecycle
* - Dynamic component creation and management
* - Event binding and emission
* - Integration with Angular change detection
*
* Use in combination with GridstackItemComponent for individual grid items.
*
* @example
* ```html
* <gridstack [options]="gridOptions" (change)="onGridChange($event)">
* <div empty-content>Drag widgets here</div>
* </gridstack>
* ```
*/
export declare class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
protected readonly elementRef: ElementRef<GridCompHTMLElement>;
/**
* List of template-based grid items (not recommended approach).
* Used to sync between DOM and GridStack internals when items are defined in templates.
* Prefer dynamic component creation instead.
*/
gridstackItems?: QueryList<GridstackItemComponent>;
/**
* Container for dynamic component creation (recommended approach).
* Used to append grid items programmatically at runtime.
*/
container?: ViewContainerRef;
/**
* Grid configuration options.
* Can be set before grid initialization or updated after grid is created.
*
* @example
* ```typescript
* gridOptions: GridStackOptions = {
* column: 12,
* cellHeight: 'auto',
* animate: true
* };
* ```
*/
set options(o: GridStackOptions);
/** Get the current running grid options */
get options(): GridStackOptions;
/**
* Controls whether empty content should be displayed.
* Set to true to show ng-content with 'empty-content' selector when grid has no items.
*
* @example
* ```html
* <gridstack [isEmpty]="gridItems.length === 0">
* <div empty-content>Drag widgets here to get started</div>
* </gridstack>
* ```
*/
isEmpty?: boolean;
/**
* GridStack event emitters for Angular integration.
*
* These provide Angular-style event handling for GridStack events.
* Alternatively, use `this.grid.on('event1 event2', callback)` for multiple events.
*
* Note: 'CB' suffix prevents conflicts with native DOM events.
*
* @example
* ```html
* <gridstack (changeCB)="onGridChange($event)" (droppedCB)="onItemDropped($event)">
* </gridstack>
* ```
*/
/** Emitted when widgets are added to the grid */
addedCB: EventEmitter<nodesCB>;
/** Emitted when grid layout changes */
changeCB: EventEmitter<nodesCB>;
/** Emitted when grid is disabled */
disableCB: EventEmitter<eventCB>;
/** Emitted during widget drag operations */
dragCB: EventEmitter<elementCB>;
/** Emitted when widget drag starts */
dragStartCB: EventEmitter<elementCB>;
/** Emitted when widget drag stops */
dragStopCB: EventEmitter<elementCB>;
/** Emitted when widget is dropped */
droppedCB: EventEmitter<droppedCB>;
/** Emitted when grid is enabled */
enableCB: EventEmitter<eventCB>;
/** Emitted when widgets are removed from the grid */
removedCB: EventEmitter<nodesCB>;
/** Emitted during widget resize operations */
resizeCB: EventEmitter<elementCB>;
/** Emitted when widget resize starts */
resizeStartCB: EventEmitter<elementCB>;
/** Emitted when widget resize stops */
resizeStopCB: EventEmitter<elementCB>;
/**
* Get the native DOM element that contains grid-specific fields.
* This element has GridStack properties attached to it.
*/
get el(): GridCompHTMLElement;
/**
* Get the underlying GridStack instance.
* Use this to access GridStack API methods directly.
*
* @example
* ```typescript
* this.gridComponent.grid.addWidget({x: 0, y: 0, w: 2, h: 1});
* ```
*/
get grid(): GridStack | undefined;
/**
* Component reference for dynamic component removal.
* Used internally when this component is created dynamically.
*/
ref: ComponentRef<GridstackComponent> | undefined;
/**
* Mapping of component selectors to their types for dynamic creation.
*
* This enables dynamic component instantiation from string selectors.
* Angular doesn't provide public access to this mapping, so we maintain our own.
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([MyWidgetComponent]);
* ```
*/
static selectorToType: SelectorToType;
/**
* Register a list of Angular components for dynamic creation.
*
* @param typeList Array of component types to register
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([
* MyWidgetComponent,
* AnotherWidgetComponent
* ]);
* ```
*/
static addComponentToSelectorType(typeList: Array<Type<Object>>): void;
/**
* Extract the selector string from an Angular component type.
*
* @param type The component type to get selector from
* @returns The component's selector string
*/
static getSelector(type: Type<Object>): string;
protected _options?: GridStackOptions;
protected _grid?: GridStack;
protected _sub: Subscription | undefined;
protected loaded?: boolean;
constructor(elementRef: ElementRef<GridCompHTMLElement>);
ngOnInit(): void;
/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
ngAfterContentInit(): void;
ngOnDestroy(): void;
/**
* called when the TEMPLATE (not recommended) list of items changes - get a list of nodes and
* update the layout accordingly (which will take care of adding/removing items changed by Angular)
*/
updateAll(): void;
/** check if the grid is empty, if so show alternative content */
checkEmpty(): void;
/** get all known events as easy to use Outputs for convenience */
protected hookEvents(grid?: GridStack): void;
protected unhookEvents(grid?: GridStack): void;
static ɵfac: i0.ɵɵFactoryDeclaration<GridstackComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<GridstackComponent, "gridstack", never, { "options": "options"; "isEmpty": "isEmpty"; }, { "addedCB": "addedCB"; "changeCB": "changeCB"; "disableCB": "disableCB"; "dragCB": "dragCB"; "dragStartCB": "dragStartCB"; "dragStopCB": "dragStopCB"; "droppedCB": "droppedCB"; "enableCB": "enableCB"; "removedCB": "removedCB"; "resizeCB": "resizeCB"; "resizeStartCB": "resizeStartCB"; "resizeStopCB": "resizeStopCB"; }, ["gridstackItems"], ["[empty-content]", "*"], true>;
}
/**
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
**/
export declare function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, n: NgGridStackNode, add: boolean, isGrid: boolean): HTMLElement | undefined;
/**
* called for each item in the grid - check if additional information needs to be saved.
* Note: since this is options minus gridstack protected members using Utils.removeInternalForSave(),
* this typically doesn't need to do anything. However your custom Component @Input() are now supported
* using BaseWidget.serialize()
*/
export declare function gsSaveAdditionalNgInfo(n: NgGridStackNode, w: NgGridStackWidget): void;
/**
* track when widgeta re updated (rather than created) to make sure we de-serialize them as well
*/
export declare function gsUpdateNgComponents(n: NgGridStackNode): void;

View File

@@ -1,31 +0,0 @@
import * as i0 from "@angular/core";
import * as i1 from "./gridstack-item.component";
import * as i2 from "./gridstack.component";
/**
* @deprecated Use GridstackComponent and GridstackItemComponent as standalone components instead.
*
* This NgModule is provided for backward compatibility but is no longer the recommended approach.
* Import components directly in your standalone components or use the new Angular module structure.
*
* @example
* ```typescript
* // Preferred approach - standalone components
* @Component({
* selector: 'my-app',
* imports: [GridstackComponent, GridstackItemComponent],
* template: '<gridstack></gridstack>'
* })
* export class AppComponent {}
*
* // Legacy approach (deprecated)
* @NgModule({
* imports: [GridstackModule]
* })
* export class AppModule {}
* ```
*/
export declare class GridstackModule {
static ɵfac: i0.ɵɵFactoryDeclaration<GridstackModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<GridstackModule, never, [typeof i1.GridstackItemComponent, typeof i2.GridstackComponent], [typeof i1.GridstackItemComponent, typeof i2.GridstackComponent]>;
static ɵinj: i0.ɵɵInjectorDeclaration<GridstackModule>;
}

View File

@@ -1,51 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2025 Alain Dumesny - see GridStack root license
*/
import { GridStackNode, GridStackOptions, GridStackWidget } from "gridstack";
/**
* Extended GridStackWidget interface for Angular integration.
* Adds Angular-specific properties for dynamic component creation.
*/
export interface NgGridStackWidget extends GridStackWidget {
/** Angular component selector for dynamic creation (e.g., 'my-widget') */
selector?: string;
/** Serialized data for component @Input() properties */
input?: NgCompInputs;
/** Configuration for nested sub-grids */
subGridOpts?: NgGridStackOptions;
}
/**
* Extended GridStackNode interface for Angular integration.
* Adds component selector for dynamic content creation.
*/
export interface NgGridStackNode extends GridStackNode {
/** Angular component selector for this node's content */
selector?: string;
}
/**
* Extended GridStackOptions interface for Angular integration.
* Supports Angular-specific widget definitions and nested grids.
*/
export interface NgGridStackOptions extends GridStackOptions {
/** Array of Angular widget definitions for initial grid setup */
children?: NgGridStackWidget[];
/** Configuration for nested sub-grids (Angular-aware) */
subGridOpts?: NgGridStackOptions;
}
/**
* Type for component input data serialization.
* Maps @Input() property names to their values for widget persistence.
*
* @example
* ```typescript
* const inputs: NgCompInputs = {
* title: 'My Widget',
* value: 42,
* config: { enabled: true }
* };
* ```
*/
export declare type NgCompInputs = {
[key: string]: any;
};

View File

@@ -1,31 +0,0 @@
{
"name": "gridstack-angular",
"version": "12.3.3",
"peerDependencies": {
"@angular/common": ">=14",
"@angular/core": ">=14"
},
"dependencies": {
"tslib": "^2.3.0"
},
"module": "fesm2015/gridstack-angular.mjs",
"es2020": "fesm2020/gridstack-angular.mjs",
"esm2020": "esm2020/gridstack-angular.mjs",
"fesm2020": "fesm2020/gridstack-angular.mjs",
"fesm2015": "fesm2015/gridstack-angular.mjs",
"typings": "index.d.ts",
"exports": {
"./package.json": {
"default": "./package.json"
},
".": {
"types": "./index.d.ts",
"esm2020": "./esm2020/gridstack-angular.mjs",
"es2020": "./fesm2020/gridstack-angular.mjs",
"es2015": "./fesm2015/gridstack-angular.mjs",
"node": "./fesm2015/gridstack-angular.mjs",
"default": "./fesm2020/gridstack-angular.mjs"
}
},
"sideEffects": false
}

View File

@@ -1,95 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
/**
* Abstract base class that all custom widgets should extend.
*
* This class provides the interface needed for GridstackItemComponent to:
* - Serialize/deserialize widget data
* - Save/restore widget state
* - Integrate with Angular lifecycle
*
* Extend this class when creating custom widgets for dynamic grids.
*
* @example
* ```typescript
* @Component({
* selector: 'my-custom-widget',
* template: '<div>{{data}}</div>'
* })
* export class MyCustomWidget extends BaseWidget {
* @Input() data: string = '';
*
* serialize() {
* return { data: this.data };
* }
* }
* ```
*/
import { Injectable } from '@angular/core';
import { NgCompInputs, NgGridStackWidget } from './types';
/**
* Base widget class for GridStack Angular integration.
*/
@Injectable()
export abstract class BaseWidget {
/**
* Complete widget definition including position, size, and Angular-specific data.
* Populated automatically when the widget is loaded or saved.
*/
public widgetItem?: NgGridStackWidget;
/**
* Override this method to return serializable data for this widget.
*
* Return an object with properties that map to your component's @Input() fields.
* The selector is handled automatically, so only include component-specific data.
*
* @returns Object containing serializable component data
*
* @example
* ```typescript
* serialize() {
* return {
* title: this.title,
* value: this.value,
* settings: this.settings
* };
* }
* ```
*/
public serialize(): NgCompInputs | undefined { return; }
/**
* Override this method to handle widget restoration from saved data.
*
* Use this for complex initialization that goes beyond simple @Input() mapping.
* The default implementation automatically assigns input data to component properties.
*
* @param w The saved widget data including input properties
*
* @example
* ```typescript
* deserialize(w: NgGridStackWidget) {
* super.deserialize(w); // Call parent for basic setup
*
* // Custom initialization logic
* if (w.input?.complexData) {
* this.processComplexData(w.input.complexData);
* }
* }
* ```
*/
public deserialize(w: NgGridStackWidget) {
// save full description for meta data
this.widgetItem = w;
if (!w) return;
if (w.input) Object.assign(this, w.input);
}
}

View File

@@ -1,125 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import { Component, ElementRef, Input, ViewChild, ViewContainerRef, OnDestroy, ComponentRef } from '@angular/core';
import { GridItemHTMLElement, GridStackNode } from 'gridstack';
import { BaseWidget } from './base-widget';
/**
* Extended HTMLElement interface for grid items.
* Stores a back-reference to the Angular component for integration.
*/
export interface GridItemCompHTMLElement extends GridItemHTMLElement {
/** Back-reference to the Angular GridStackItem component */
_gridItemComp?: GridstackItemComponent;
}
/**
* Angular component wrapper for individual GridStack items.
*
* This component represents a single grid item and handles:
* - Dynamic content creation and management
* - Integration with parent GridStack component
* - Component lifecycle and cleanup
* - Widget options and configuration
*
* Use in combination with GridstackComponent for the parent grid.
*
* @example
* ```html
* <gridstack>
* <gridstack-item [options]="{x: 0, y: 0, w: 2, h: 1}">
* <my-widget-component></my-widget-component>
* </gridstack-item>
* </gridstack>
* ```
*/
@Component({
selector: 'gridstack-item',
template: `
<div class="grid-stack-item-content">
<!-- where dynamic items go based on component selector (recommended way), or sub-grids, etc...) -->
<ng-template #container></ng-template>
<!-- any static (defined in DOM - not recommended) content goes here -->
<ng-content></ng-content>
<!-- fallback HTML content from GridStackWidget.content if used instead (not recommended) -->
{{options.content}}
</div>`,
styles: [`
:host { display: block; }
`],
standalone: true,
// changeDetection: ChangeDetectionStrategy.OnPush, // IFF you want to optimize and control when ChangeDetection needs to happen...
})
export class GridstackItemComponent implements OnDestroy {
/**
* Container for dynamic component creation within this grid item.
* Used to append child components programmatically.
*/
@ViewChild('container', { read: ViewContainerRef, static: true}) public container?: ViewContainerRef;
/**
* Component reference for dynamic component removal.
* Used internally when this component is created dynamically.
*/
public ref: ComponentRef<GridstackItemComponent> | undefined;
/**
* Reference to child widget component for serialization.
* Used to save/restore additional data along with grid position.
*/
public childWidget: BaseWidget | undefined;
/**
* Grid item configuration options.
* Defines position, size, and behavior of this grid item.
*
* @example
* ```typescript
* itemOptions: GridStackNode = {
* x: 0, y: 0, w: 2, h: 1,
* noResize: true,
* content: 'Item content'
* };
* ```
*/
@Input() public set options(val: GridStackNode) {
const grid = this.el.gridstackNode?.grid;
if (grid) {
// already built, do an update...
grid.update(this.el, val);
} else {
// store our custom element in options so we can update it and not re-create a generic div!
this._options = {...val, el: this.el};
}
}
/** return the latest grid options (from GS once built, otherwise initial values) */
public get options(): GridStackNode {
return this.el.gridstackNode || this._options || {el: this.el};
}
protected _options?: GridStackNode;
/** return the native element that contains grid specific fields as well */
public get el(): GridItemCompHTMLElement { return this.elementRef.nativeElement; }
/** clears the initial options now that we've built */
public clearOptions() {
delete this._options;
}
constructor(protected readonly elementRef: ElementRef<GridItemCompHTMLElement>) {
this.el._gridItemComp = this;
}
public ngOnDestroy(): void {
this.clearOptions();
delete this.childWidget
delete this.el._gridItemComp;
delete this.container;
delete this.ref;
}
}

View File

@@ -1,460 +0,0 @@
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import {
AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, Input,
OnDestroy, OnInit, Output, QueryList, Type, ViewChild, ViewContainerRef, reflectComponentType, ComponentRef
} from '@angular/core';
import { NgIf } from '@angular/common';
import { Subscription } from 'rxjs';
import { GridHTMLElement, GridItemHTMLElement, GridStack, GridStackNode, GridStackOptions, GridStackWidget } from 'gridstack';
import { NgGridStackNode, NgGridStackWidget } from './types';
import { BaseWidget } from './base-widget';
import { GridItemCompHTMLElement, GridstackItemComponent } from './gridstack-item.component';
/**
* Event handler callback signatures for different GridStack events.
* These types define the structure of data passed to Angular event emitters.
*/
/** Callback for general events (enable, disable, etc.) */
export type eventCB = {event: Event};
/** Callback for element-specific events (resize, drag, etc.) */
export type elementCB = {event: Event, el: GridItemHTMLElement};
/** Callback for events affecting multiple nodes (change, etc.) */
export type nodesCB = {event: Event, nodes: GridStackNode[]};
/** Callback for drop events with before/after node state */
export type droppedCB = {event: Event, previousNode: GridStackNode, newNode: GridStackNode};
/**
* Extended HTMLElement interface for the grid container.
* Stores a back-reference to the Angular component for integration purposes.
*/
export interface GridCompHTMLElement extends GridHTMLElement {
/** Back-reference to the Angular GridStack component */
_gridComp?: GridstackComponent;
}
/**
* Mapping of selector strings to Angular component types.
* Used for dynamic component creation based on widget selectors.
*/
export type SelectorToType = {[key: string]: Type<Object>};
/**
* Angular component wrapper for GridStack.
*
* This component provides Angular integration for GridStack grids, handling:
* - Grid initialization and lifecycle
* - Dynamic component creation and management
* - Event binding and emission
* - Integration with Angular change detection
*
* Use in combination with GridstackItemComponent for individual grid items.
*
* @example
* ```html
* <gridstack [options]="gridOptions" (change)="onGridChange($event)">
* <div empty-content>Drag widgets here</div>
* </gridstack>
* ```
*/
@Component({
selector: 'gridstack',
template: `
<!-- content to show when when grid is empty, like instructions on how to add widgets -->
<ng-content select="[empty-content]" *ngIf="isEmpty"></ng-content>
<!-- where dynamic items go -->
<ng-template #container></ng-template>
<!-- where template items go -->
<ng-content></ng-content>
`,
styles: [`
:host { display: block; }
`],
standalone: true,
imports: [NgIf]
// changeDetection: ChangeDetectionStrategy.OnPush, // IFF you want to optimize and control when ChangeDetection needs to happen...
})
export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
/**
* List of template-based grid items (not recommended approach).
* Used to sync between DOM and GridStack internals when items are defined in templates.
* Prefer dynamic component creation instead.
*/
@ContentChildren(GridstackItemComponent) public gridstackItems?: QueryList<GridstackItemComponent>;
/**
* Container for dynamic component creation (recommended approach).
* Used to append grid items programmatically at runtime.
*/
@ViewChild('container', { read: ViewContainerRef, static: true}) public container?: ViewContainerRef;
/**
* Grid configuration options.
* Can be set before grid initialization or updated after grid is created.
*
* @example
* ```typescript
* gridOptions: GridStackOptions = {
* column: 12,
* cellHeight: 'auto',
* animate: true
* };
* ```
*/
@Input() public set options(o: GridStackOptions) {
if (this._grid) {
this._grid.updateOptions(o);
} else {
this._options = o;
}
}
/** Get the current running grid options */
public get options(): GridStackOptions { return this._grid?.opts || this._options || {}; }
/**
* Controls whether empty content should be displayed.
* Set to true to show ng-content with 'empty-content' selector when grid has no items.
*
* @example
* ```html
* <gridstack [isEmpty]="gridItems.length === 0">
* <div empty-content>Drag widgets here to get started</div>
* </gridstack>
* ```
*/
@Input() public isEmpty?: boolean;
/**
* GridStack event emitters for Angular integration.
*
* These provide Angular-style event handling for GridStack events.
* Alternatively, use `this.grid.on('event1 event2', callback)` for multiple events.
*
* Note: 'CB' suffix prevents conflicts with native DOM events.
*
* @example
* ```html
* <gridstack (changeCB)="onGridChange($event)" (droppedCB)="onItemDropped($event)">
* </gridstack>
* ```
*/
/** Emitted when widgets are added to the grid */
@Output() public addedCB = new EventEmitter<nodesCB>();
/** Emitted when grid layout changes */
@Output() public changeCB = new EventEmitter<nodesCB>();
/** Emitted when grid is disabled */
@Output() public disableCB = new EventEmitter<eventCB>();
/** Emitted during widget drag operations */
@Output() public dragCB = new EventEmitter<elementCB>();
/** Emitted when widget drag starts */
@Output() public dragStartCB = new EventEmitter<elementCB>();
/** Emitted when widget drag stops */
@Output() public dragStopCB = new EventEmitter<elementCB>();
/** Emitted when widget is dropped */
@Output() public droppedCB = new EventEmitter<droppedCB>();
/** Emitted when grid is enabled */
@Output() public enableCB = new EventEmitter<eventCB>();
/** Emitted when widgets are removed from the grid */
@Output() public removedCB = new EventEmitter<nodesCB>();
/** Emitted during widget resize operations */
@Output() public resizeCB = new EventEmitter<elementCB>();
/** Emitted when widget resize starts */
@Output() public resizeStartCB = new EventEmitter<elementCB>();
/** Emitted when widget resize stops */
@Output() public resizeStopCB = new EventEmitter<elementCB>();
/**
* Get the native DOM element that contains grid-specific fields.
* This element has GridStack properties attached to it.
*/
public get el(): GridCompHTMLElement { return this.elementRef.nativeElement; }
/**
* Get the underlying GridStack instance.
* Use this to access GridStack API methods directly.
*
* @example
* ```typescript
* this.gridComponent.grid.addWidget({x: 0, y: 0, w: 2, h: 1});
* ```
*/
public get grid(): GridStack | undefined { return this._grid; }
/**
* Component reference for dynamic component removal.
* Used internally when this component is created dynamically.
*/
public ref: ComponentRef<GridstackComponent> | undefined;
/**
* Mapping of component selectors to their types for dynamic creation.
*
* This enables dynamic component instantiation from string selectors.
* Angular doesn't provide public access to this mapping, so we maintain our own.
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([MyWidgetComponent]);
* ```
*/
public static selectorToType: SelectorToType = {};
/**
* Register a list of Angular components for dynamic creation.
*
* @param typeList Array of component types to register
*
* @example
* ```typescript
* GridstackComponent.addComponentToSelectorType([
* MyWidgetComponent,
* AnotherWidgetComponent
* ]);
* ```
*/
public static addComponentToSelectorType(typeList: Array<Type<Object>>) {
typeList.forEach(type => GridstackComponent.selectorToType[ GridstackComponent.getSelector(type) ] = type);
}
/**
* Extract the selector string from an Angular component type.
*
* @param type The component type to get selector from
* @returns The component's selector string
*/
public static getSelector(type: Type<Object>): string {
return reflectComponentType(type)!.selector;
}
protected _options?: GridStackOptions;
protected _grid?: GridStack;
protected _sub: Subscription | undefined;
protected loaded?: boolean;
constructor(protected readonly elementRef: ElementRef<GridCompHTMLElement>) {
// set globally our method to create the right widget type
if (!GridStack.addRemoveCB) {
GridStack.addRemoveCB = gsCreateNgComponents;
}
if (!GridStack.saveCB) {
GridStack.saveCB = gsSaveAdditionalNgInfo;
}
if (!GridStack.updateCB) {
GridStack.updateCB = gsUpdateNgComponents;
}
this.el._gridComp = this;
}
public ngOnInit(): void {
// init ourself before any template children are created since we track them below anyway - no need to double create+update widgets
this.loaded = !!this.options?.children?.length;
this._grid = GridStack.init(this._options, this.el);
delete this._options; // GS has it now
this.checkEmpty();
}
/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
public ngAfterContentInit(): void {
// track whenever the children list changes and update the layout...
this._sub = this.gridstackItems?.changes.subscribe(() => this.updateAll());
// ...and do this once at least unless we loaded children already
if (!this.loaded) this.updateAll();
this.hookEvents(this.grid);
}
public ngOnDestroy(): void {
this.unhookEvents(this._grid);
this._sub?.unsubscribe();
this._grid?.destroy();
delete this._grid;
delete this.el._gridComp;
delete this.container;
delete this.ref;
}
/**
* called when the TEMPLATE (not recommended) list of items changes - get a list of nodes and
* update the layout accordingly (which will take care of adding/removing items changed by Angular)
*/
public updateAll() {
if (!this.grid) return;
const layout: GridStackWidget[] = [];
this.gridstackItems?.forEach(item => {
layout.push(item.options);
item.clearOptions();
});
this.grid.load(layout); // efficient that does diffs only
}
/** check if the grid is empty, if so show alternative content */
public checkEmpty() {
if (!this.grid) return;
this.isEmpty = !this.grid.engine.nodes.length;
}
/** get all known events as easy to use Outputs for convenience */
protected hookEvents(grid?: GridStack) {
if (!grid) return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode) return;
grid
.on('added', (event: Event, nodes: GridStackNode[]) => {
const gridComp = (nodes[0].grid?.el as GridCompHTMLElement)._gridComp || this;
gridComp.checkEmpty();
this.addedCB.emit({event, nodes});
})
.on('change', (event: Event, nodes: GridStackNode[]) => this.changeCB.emit({event, nodes}))
.on('disable', (event: Event) => this.disableCB.emit({event}))
.on('drag', (event: Event, el: GridItemHTMLElement) => this.dragCB.emit({event, el}))
.on('dragstart', (event: Event, el: GridItemHTMLElement) => this.dragStartCB.emit({event, el}))
.on('dragstop', (event: Event, el: GridItemHTMLElement) => this.dragStopCB.emit({event, el}))
.on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.droppedCB.emit({event, previousNode, newNode}))
.on('enable', (event: Event) => this.enableCB.emit({event}))
.on('removed', (event: Event, nodes: GridStackNode[]) => {
const gridComp = (nodes[0].grid?.el as GridCompHTMLElement)._gridComp || this;
gridComp.checkEmpty();
this.removedCB.emit({event, nodes});
})
.on('resize', (event: Event, el: GridItemHTMLElement) => this.resizeCB.emit({event, el}))
.on('resizestart', (event: Event, el: GridItemHTMLElement) => this.resizeStartCB.emit({event, el}))
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.resizeStopCB.emit({event, el}))
}
protected unhookEvents(grid?: GridStack) {
if (!grid) return;
// nested grids don't have events in v12.1+ so skip
if (grid.parentGridNode) return;
grid.off('added change disable drag dragstart dragstop dropped enable removed resize resizestart resizestop');
}
}
/**
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
**/
export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, n: NgGridStackNode, add: boolean, isGrid: boolean): HTMLElement | undefined {
if (add) {
//
// create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
//
if (!host) return;
if (isGrid) {
// TODO: figure out how to create ng component inside regular Div. need to access app injectors...
// if (!container) {
// const hostElement: Element = host;
// const environmentInjector: EnvironmentInjector;
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
// }
const gridItemComp = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp;
if (!gridItemComp) return;
// check if gridItem has a child component with 'container' exposed to create under..
const container = (gridItemComp.childWidget as any)?.container || gridItemComp.container;
const gridRef = container?.createComponent(GridstackComponent);
const grid = gridRef?.instance;
if (!grid) return;
grid.ref = gridRef;
grid.options = n;
return grid.el;
} else {
const gridComp = (host as GridCompHTMLElement)._gridComp;
const gridItemRef = gridComp?.container?.createComponent(GridstackItemComponent);
const gridItem = gridItemRef?.instance;
if (!gridItem) return;
gridItem.ref = gridItemRef
// define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
const selector = n.selector;
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
if (type) {
// shared code to create our selector component
const createComp = () => {
const childWidget = gridItem.container?.createComponent(type)?.instance as BaseWidget;
// if proper BaseWidget subclass, save it and load additional data
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
gridItem.childWidget = childWidget;
childWidget.deserialize(n);
}
}
const lazyLoad = n.lazyLoad || n.grid?.opts?.lazyLoad && n.lazyLoad !== false;
if (lazyLoad) {
if (!n.visibleObservable) {
n.visibleObservable = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) {
n.visibleObservable?.disconnect();
delete n.visibleObservable;
createComp();
}});
window.setTimeout(() => n.visibleObservable?.observe(gridItem.el)); // wait until callee sets position attributes
}
} else createComp();
}
return gridItem.el;
}
} else {
//
// REMOVE - have to call ComponentRef:destroy() for dynamic objects to correctly remove themselves
// Note: this will destroy all children dynamic components as well: gridItem -> childWidget
//
if (isGrid) {
const grid = (n.el as GridCompHTMLElement)?._gridComp;
if (grid?.ref) grid.ref.destroy();
else grid?.ngOnDestroy();
} else {
const gridItem = (n.el as GridItemCompHTMLElement)?._gridItemComp;
if (gridItem?.ref) gridItem.ref.destroy();
else gridItem?.ngOnDestroy();
}
}
return;
}
/**
* called for each item in the grid - check if additional information needs to be saved.
* Note: since this is options minus gridstack protected members using Utils.removeInternalForSave(),
* this typically doesn't need to do anything. However your custom Component @Input() are now supported
* using BaseWidget.serialize()
*/
export function gsSaveAdditionalNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
const gridItem = (n.el as GridItemCompHTMLElement)?._gridItemComp;
if (gridItem) {
const input = gridItem.childWidget?.serialize();
if (input) {
w.input = input;
}
return;
}
// else check if Grid
const grid = (n.el as GridCompHTMLElement)?._gridComp;
if (grid) {
//.... save any custom data
}
}
/**
* track when widgeta re updated (rather than created) to make sure we de-serialize them as well
*/
export function gsUpdateNgComponents(n: NgGridStackNode) {
const w: NgGridStackWidget = n;
const gridItem = (n.el as GridItemCompHTMLElement)?._gridItemComp;
if (gridItem?.childWidget && w.input) gridItem.childWidget.deserialize(w);
}

View File

@@ -1,44 +0,0 @@
/**
* gridstack.component.ts 12.3.3
* Copyright (c) 2022-2024 Alain Dumesny - see GridStack root license
*/
import { NgModule } from "@angular/core";
import { GridstackItemComponent } from "./gridstack-item.component";
import { GridstackComponent } from "./gridstack.component";
/**
* @deprecated Use GridstackComponent and GridstackItemComponent as standalone components instead.
*
* This NgModule is provided for backward compatibility but is no longer the recommended approach.
* Import components directly in your standalone components or use the new Angular module structure.
*
* @example
* ```typescript
* // Preferred approach - standalone components
* @Component({
* selector: 'my-app',
* imports: [GridstackComponent, GridstackItemComponent],
* template: '<gridstack></gridstack>'
* })
* export class AppComponent {}
*
* // Legacy approach (deprecated)
* @NgModule({
* imports: [GridstackModule]
* })
* export class AppModule {}
* ```
*/
@NgModule({
imports: [
GridstackItemComponent,
GridstackComponent,
],
exports: [
GridstackItemComponent,
GridstackComponent,
],
})
export class GridstackModule {}

View File

@@ -1,54 +0,0 @@
/**
* gridstack-item.component.ts 12.3.3
* Copyright (c) 2025 Alain Dumesny - see GridStack root license
*/
import { GridStackNode, GridStackOptions, GridStackWidget } from "gridstack";
/**
* Extended GridStackWidget interface for Angular integration.
* Adds Angular-specific properties for dynamic component creation.
*/
export interface NgGridStackWidget extends GridStackWidget {
/** Angular component selector for dynamic creation (e.g., 'my-widget') */
selector?: string;
/** Serialized data for component @Input() properties */
input?: NgCompInputs;
/** Configuration for nested sub-grids */
subGridOpts?: NgGridStackOptions;
}
/**
* Extended GridStackNode interface for Angular integration.
* Adds component selector for dynamic content creation.
*/
export interface NgGridStackNode extends GridStackNode {
/** Angular component selector for this node's content */
selector?: string;
}
/**
* Extended GridStackOptions interface for Angular integration.
* Supports Angular-specific widget definitions and nested grids.
*/
export interface NgGridStackOptions extends GridStackOptions {
/** Array of Angular widget definitions for initial grid setup */
children?: NgGridStackWidget[];
/** Configuration for nested sub-grids (Angular-aware) */
subGridOpts?: NgGridStackOptions;
}
/**
* Type for component input data serialization.
* Maps @Input() property names to their values for widget persistence.
*
* @example
* ```typescript
* const inputs: NgCompInputs = {
* title: 'My Widget',
* value: 42,
* config: { enabled: true }
* };
* ```
*/
export type NgCompInputs = {[key: string]: any};

View File

@@ -1,5 +1,5 @@
/**
* dd-base-impl.ts 12.3.2
* dd-base-impl.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**

View File

@@ -1,5 +1,5 @@
/**
* dd-base-impl.ts 12.3.2
* dd-base-impl.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**

View File

@@ -1 +1 @@
{"version":3,"file":"dd-base-impl.js","sourceRoot":"","sources":["../src/dd-base-impl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;;GAIG;AACH,MAAM,OAAgB,eAAe;IAArC;QASE,gBAAgB;QACN,mBAAc,GAEpB,EAAE,CAAC;IAwDT,CAAC;IAnEC;;;OAGG;IACH,IAAW,QAAQ,KAAgB,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAS3D;;;;;OAKG;IACI,EAAE,CAAC,KAAa,EAAE,QAAuB;QAC9C,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,KAAa;QACtB,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACI,MAAM;QACX,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACI,YAAY,CAAC,SAAiB,EAAE,KAAY;QACjD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;YACzE,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;CACF","sourcesContent":["/**\n * dd-base-impl.ts 12.3.2\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\n/**\n * Type for event callback functions used in drag & drop operations.\n * Can return boolean to indicate if the event should continue propagation.\n */\nexport type EventCallback = (event: Event) => boolean|void;\n\n/**\n * Abstract base class for all drag & drop implementations.\n * Provides common functionality for event handling, enable/disable state,\n * and lifecycle management used by draggable, droppable, and resizable implementations.\n */\nexport abstract class DDBaseImplement {\n /**\n * Returns the current disabled state.\n * Note: Use enable()/disable() methods to change state as other operations need to happen.\n */\n public get disabled(): boolean { return this._disabled; }\n\n /** @internal */\n protected _disabled: boolean; // initial state to differentiate from false\n /** @internal */\n protected _eventRegister: {\n [eventName: string]: EventCallback;\n } = {};\n\n /**\n * Register an event callback for the specified event.\n * \n * @param event - Event name to listen for\n * @param callback - Function to call when event occurs\n */\n public on(event: string, callback: EventCallback): void {\n this._eventRegister[event] = callback;\n }\n\n /**\n * Unregister an event callback for the specified event.\n * \n * @param event - Event name to stop listening for\n */\n public off(event: string): void {\n delete this._eventRegister[event];\n }\n\n /**\n * Enable this drag & drop implementation.\n * Subclasses should override to perform additional setup.\n */\n public enable(): void {\n this._disabled = false;\n }\n\n /**\n * Disable this drag & drop implementation.\n * Subclasses should override to perform additional cleanup.\n */\n public disable(): void {\n this._disabled = true;\n }\n\n /**\n * Destroy this drag & drop implementation and clean up resources.\n * Removes all event handlers and clears internal state.\n */\n public destroy(): void {\n delete this._eventRegister;\n }\n\n /**\n * Trigger a registered event callback if one exists and the implementation is enabled.\n * \n * @param eventName - Name of the event to trigger\n * @param event - DOM event object to pass to the callback\n * @returns Result from the callback function, if any\n */\n public triggerEvent(eventName: string, event: Event): boolean|void {\n if (!this.disabled && this._eventRegister && this._eventRegister[eventName])\n return this._eventRegister[eventName](event);\n }\n}\n\n/**\n * Interface for HTML elements extended with drag & drop options.\n * Used to associate DD configuration with DOM elements.\n */\nexport interface HTMLElementExtendOpt<T> {\n /** The HTML element being extended */\n el: HTMLElement;\n /** The drag & drop options/configuration */\n option: T;\n /** Method to update the options and return the DD implementation */\n updateOption(T): DDBaseImplement;\n}\n"]}
{"version":3,"file":"dd-base-impl.js","sourceRoot":"","sources":["../src/dd-base-impl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;;GAIG;AACH,MAAM,OAAgB,eAAe;IAArC;QASE,gBAAgB;QACN,mBAAc,GAEpB,EAAE,CAAC;IAwDT,CAAC;IAnEC;;;OAGG;IACH,IAAW,QAAQ,KAAgB,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAS3D;;;;;OAKG;IACI,EAAE,CAAC,KAAa,EAAE,QAAuB;QAC9C,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,KAAa;QACtB,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACI,MAAM;QACX,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACI,YAAY,CAAC,SAAiB,EAAE,KAAY;QACjD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;YACzE,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;CACF","sourcesContent":["/**\n * dd-base-impl.ts 12.4.2\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\n/**\n * Type for event callback functions used in drag & drop operations.\n * Can return boolean to indicate if the event should continue propagation.\n */\nexport type EventCallback = (event: Event) => boolean|void;\n\n/**\n * Abstract base class for all drag & drop implementations.\n * Provides common functionality for event handling, enable/disable state,\n * and lifecycle management used by draggable, droppable, and resizable implementations.\n */\nexport abstract class DDBaseImplement {\n /**\n * Returns the current disabled state.\n * Note: Use enable()/disable() methods to change state as other operations need to happen.\n */\n public get disabled(): boolean { return this._disabled; }\n\n /** @internal */\n protected _disabled: boolean; // initial state to differentiate from false\n /** @internal */\n protected _eventRegister: {\n [eventName: string]: EventCallback;\n } = {};\n\n /**\n * Register an event callback for the specified event.\n *\n * @param event - Event name to listen for\n * @param callback - Function to call when event occurs\n */\n public on(event: string, callback: EventCallback): void {\n this._eventRegister[event] = callback;\n }\n\n /**\n * Unregister an event callback for the specified event.\n *\n * @param event - Event name to stop listening for\n */\n public off(event: string): void {\n delete this._eventRegister[event];\n }\n\n /**\n * Enable this drag & drop implementation.\n * Subclasses should override to perform additional setup.\n */\n public enable(): void {\n this._disabled = false;\n }\n\n /**\n * Disable this drag & drop implementation.\n * Subclasses should override to perform additional cleanup.\n */\n public disable(): void {\n this._disabled = true;\n }\n\n /**\n * Destroy this drag & drop implementation and clean up resources.\n * Removes all event handlers and clears internal state.\n */\n public destroy(): void {\n delete this._eventRegister;\n }\n\n /**\n * Trigger a registered event callback if one exists and the implementation is enabled.\n *\n * @param eventName - Name of the event to trigger\n * @param event - DOM event object to pass to the callback\n * @returns Result from the callback function, if any\n */\n public triggerEvent(eventName: string, event: Event): boolean|void {\n if (!this.disabled && this._eventRegister && this._eventRegister[eventName])\n return this._eventRegister[eventName](event);\n }\n}\n\n/**\n * Interface for HTML elements extended with drag & drop options.\n * Used to associate DD configuration with DOM elements.\n */\nexport interface HTMLElementExtendOpt<T> {\n /** The HTML element being extended */\n el: HTMLElement;\n /** The drag & drop options/configuration */\n option: T;\n /** Method to update the options and return the DD implementation */\n updateOption(T): DDBaseImplement;\n}\n"]}

View File

@@ -1,5 +1,5 @@
/**
* dd-draggable.ts 12.3.2
* dd-draggable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';

View File

@@ -1,11 +1,11 @@
/**
* dd-draggable.ts 12.3.2
* dd-draggable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDManager } from './dd-manager';
import { Utils } from './utils';
import { DDBaseImplement } from './dd-base-impl';
import { isTouch, touchend, touchmove, touchstart, pointerdown } from './dd-touch';
import { isTouch, touchend, touchmove, touchstart, pointerdown, DDTouch } from './dd-touch';
// make sure we are not clicking on known object that handles mouseDown
const skipMouseDown = 'input,textarea,button,select,option,[contenteditable="true"],.ui-resizable-handle';
// let count = 0; // TEST
@@ -87,6 +87,9 @@ class DDDraggable extends DDBaseImplement {
}
/** @internal call when mouse goes down before a dragstart happens */
_mouseDown(e) {
// if real brower event (trusted:true vs false for our simulated ones) and we didn't correctly clear the last touch event, clear things up
if (DDTouch.touchHandled && e.isTrusted)
DDTouch.touchHandled = false;
// don't let more than one widget handle mouseStart
if (DDManager.mouseHandled)
return;

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* dd-droppable.ts 12.3.2
* dd-droppable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';

View File

@@ -1,11 +1,11 @@
/**
* dd-droppable.ts 12.3.2
* dd-droppable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDManager } from './dd-manager';
import { DDBaseImplement } from './dd-base-impl';
import { Utils } from './utils';
import { isTouch, pointerenter, pointerleave } from './dd-touch';
import { DDTouch, isTouch, pointerenter, pointerleave } from './dd-touch';
// let count = 0; // TEST
export class DDDroppable extends DDBaseImplement {
constructor(el, option = {}) {
@@ -67,6 +67,10 @@ export class DDDroppable extends DDBaseImplement {
// console.log(`${count++} Enter ${this.el.id || (this.el as GridHTMLElement).gridstack.opts.id}`); // TEST
if (!DDManager.dragElement)
return;
// During touch drag operations, ignore real browser-generated mouseenter events (isTrusted:true) vs our simulated ones (isTrusted:false).
// The browser can fire spurious mouseenter events when we dispatch simulated mousemove events.
if (DDTouch.touchHandled && e.isTrusted)
return;
if (!this._canDrop(DDManager.dragElement.el))
return;
e.preventDefault();

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* dd-elements.ts 12.3.2
* dd-elements.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizable, DDResizableOpt } from './dd-resizable';

View File

@@ -1,5 +1,5 @@
/**
* dd-elements.ts 12.3.2
* dd-elements.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizable } from './dd-resizable';

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* dd-gridstack.ts 12.3.2
* dd-gridstack.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { GridItemHTMLElement, GridStackElement, DDDragOpt } from './types';

View File

@@ -1,5 +1,5 @@
/**
* dd-gridstack.ts 12.3.2
* dd-gridstack.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { Utils } from './utils';

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* dd-manager.ts 12.3.2
* dd-manager.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDDraggable } from './dd-draggable';

View File

@@ -1,5 +1,5 @@
/**
* dd-manager.ts 12.3.2
* dd-manager.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**

View File

@@ -1 +1 @@
{"version":3,"file":"dd-manager.js","sourceRoot":"","sources":["../src/dd-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;CAiCrB","sourcesContent":["/**\n * dd-manager.ts 12.3.2\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\nimport { DDDraggable } from './dd-draggable';\nimport { DDDroppable } from './dd-droppable';\nimport { DDResizable } from './dd-resizable';\n\n/**\n * Global state manager for all Drag & Drop instances.\n * \n * This class maintains shared state across all drag & drop operations,\n * ensuring proper coordination between multiple grids and drag/drop elements.\n * All properties are static to provide global access throughout the DD system.\n */\nexport class DDManager {\n /**\n * Controls drag operation pausing behavior.\n * If set to true or a number (milliseconds), dragging placement and collision\n * detection will only happen after the user pauses movement.\n * This improves performance during rapid mouse movements.\n */\n public static pauseDrag: boolean | number;\n\n /**\n * Flag indicating if a mouse down event was already handled.\n * Prevents multiple handlers from processing the same mouse event.\n */\n public static mouseHandled: boolean;\n\n /**\n * Reference to the element currently being dragged.\n * Used to track the active drag operation across the system.\n */\n public static dragElement: DDDraggable;\n\n /**\n * Reference to the drop target element currently under the cursor.\n * Used to handle drop operations and hover effects.\n */\n public static dropElement: DDDroppable;\n\n /**\n * Reference to the element currently being resized.\n * Helps ignore nested grid resize handles during resize operations.\n */\n public static overResizeElement: DDResizable;\n\n}\n"]}
{"version":3,"file":"dd-manager.js","sourceRoot":"","sources":["../src/dd-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;CAiCrB","sourcesContent":["/**\n * dd-manager.ts 12.4.2\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\nimport { DDDraggable } from './dd-draggable';\nimport { DDDroppable } from './dd-droppable';\nimport { DDResizable } from './dd-resizable';\n\n/**\n * Global state manager for all Drag & Drop instances.\n *\n * This class maintains shared state across all drag & drop operations,\n * ensuring proper coordination between multiple grids and drag/drop elements.\n * All properties are static to provide global access throughout the DD system.\n */\nexport class DDManager {\n /**\n * Controls drag operation pausing behavior.\n * If set to true or a number (milliseconds), dragging placement and collision\n * detection will only happen after the user pauses movement.\n * This improves performance during rapid mouse movements.\n */\n public static pauseDrag: boolean | number;\n\n /**\n * Flag indicating if a mouse down event was already handled.\n * Prevents multiple handlers from processing the same mouse event.\n */\n public static mouseHandled: boolean;\n\n /**\n * Reference to the element currently being dragged.\n * Used to track the active drag operation across the system.\n */\n public static dragElement: DDDraggable;\n\n /**\n * Reference to the drop target element currently under the cursor.\n * Used to handle drop operations and hover effects.\n */\n public static dropElement: DDDroppable;\n\n /**\n * Reference to the element currently being resized.\n * Helps ignore nested grid resize handles during resize operations.\n */\n public static overResizeElement: DDResizable;\n\n}\n"]}

View File

@@ -1,12 +1,13 @@
/**
* dd-resizable-handle.ts 12.3.2
* dd-resizable-handle.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { GridItemHTMLElement } from './gridstack';
export interface DDResizableHandleOpt {
start?: (event: any) => void;
move?: (event: any) => void;
stop?: (event: any) => void;
element?: string | HTMLElement;
start?: (event: MouseEvent) => void;
move?: (event: MouseEvent) => void;
stop?: (event: MouseEvent) => void;
}
export declare class DDResizableHandle {
protected host: GridItemHTMLElement;

View File

@@ -1,5 +1,5 @@
/**
* dd-resizable-handle.ts 12.3.2
* dd-resizable-handle.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { isTouch, pointerdown, touchend, touchmove, touchstart } from './dd-touch';
@@ -19,12 +19,25 @@ class DDResizableHandle {
}
/** @internal */
_init() {
const el = this.el = document.createElement('div');
el.classList.add('ui-resizable-handle');
el.classList.add(`${DDResizableHandle.prefix}${this.dir}`);
el.style.zIndex = '100';
el.style.userSelect = 'none';
this.host.appendChild(this.el);
if (this.option.element) {
try {
this.el = this.option.element instanceof HTMLElement
? this.option.element
: this.host.querySelector(this.option.element);
}
catch (error) {
this.option.element = undefined; // make sure destroy handles it correctly
console.error("Query for resizeable handle failed, falling back", error);
}
}
if (!this.el) {
this.el = document.createElement('div');
this.host.appendChild(this.el);
}
this.el.classList.add('ui-resizable-handle');
this.el.classList.add(`${DDResizableHandle.prefix}${this.dir}`);
this.el.style.zIndex = '100';
this.el.style.userSelect = 'none';
this.el.addEventListener('mousedown', this._mouseDown);
if (isTouch) {
this.el.addEventListener('touchstart', touchstart);
@@ -42,7 +55,9 @@ class DDResizableHandle {
this.el.removeEventListener('touchstart', touchstart);
this.el.removeEventListener('pointerdown', pointerdown);
}
this.host.removeChild(this.el);
if (!this.option.element) {
this.host.removeChild(this.el);
}
delete this.el;
delete this.host;
return this;

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,10 @@
/**
* dd-resizable.ts 12.3.2
* dd-resizable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';
import { DDUIData, GridItemHTMLElement } from './types';
export interface DDResizableOpt {
autoHide?: boolean;
handles?: string;
import { DDResizeOpt, DDUIData, GridItemHTMLElement } from './types';
export interface DDResizableOpt extends DDResizeOpt {
maxHeight?: number;
maxHeightMoveUp?: number;
maxWidth?: number;

View File

@@ -1,5 +1,5 @@
/**
* dd-resizable.ts 12.3.2
* dd-resizable.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizableHandle } from './dd-resizable-handle';
@@ -135,15 +135,10 @@ class DDResizable extends DDBaseImplement {
this.handlers = this.option.handles.split(',')
.map(dir => dir.trim())
.map(dir => new DDResizableHandle(this.el, dir, {
start: (event) => {
this._resizeStart(event);
},
stop: (event) => {
this._resizeStop(event);
},
move: (event) => {
this._resizing(event, dir);
}
element: this.option.element,
start: (event) => this._resizeStart(event),
stop: (event) => this._resizeStop(event),
move: (event) => this._resizing(event, dir)
}));
return this;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* touch.ts 12.3.2
* touch.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**
@@ -8,6 +8,11 @@
* /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
*/
export declare const isTouch: boolean;
export declare class DDTouch {
/** set to true while we are handling touch dragging, to prevent accepting browser real mouse events (trusted:true) vs our simulated ones (trusted:false) */
static touchHandled: boolean;
static pointerLeaveTimeout: number;
}
/**
* Handle the touchstart events
* @param {Object} e The widget element's touchstart event

View File

@@ -1,5 +1,5 @@
/**
* touch.ts 12.3.2
* touch.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDManager } from './dd-manager';
@@ -19,7 +19,7 @@ export const isTouch = typeof window !== 'undefined' && typeof document !== 'und
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|| navigator.msMaxTouchPoints > 0);
// interface TouchCoord {x: number, y: number};
class DDTouch {
export class DDTouch {
}
/**
* Get the x,y position of a touch event

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/*!
* GridStack 12.3.3
* GridStack 12.4.2
* https://gridstackjs.com/
*
* Copyright (c) 2021-2025 Alain Dumesny

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* gridstack-engine.ts 12.3.2
* gridstack-engine.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { GridStackNode, GridStackPosition, GridStackMoveOpts, SaveFcn, CompactOptions } from './types';

View File

@@ -1,5 +1,5 @@
/**
* gridstack-engine.ts 12.3.2
* gridstack-engine.ts 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { Utils } from './utils';
@@ -1078,14 +1078,20 @@ class GridStackEngine {
// TODO: detect doing item 'swaps' will help instead of move (especially in 1 column mode)
if (n.y >= 0 && node.y !== node._orig.y) {
n.y += (node.y - node._orig.y);
if (n.y < 0)
n.y = 0;
}
// X changed, scale from new position
if (node.x !== node._orig.x) {
n.x = Math.round(node.x * ratio);
if (n.x < 0)
n.x = 0;
}
// width changed, scale from new width
if (node.w !== node._orig.w) {
n.w = Math.round(node.w * ratio);
if (n.w < 1)
n.w = 1;
}
// ...height always carries over from cache
});

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* gridstack SASS styles 12.3.3
* gridstack SASS styles 12.4.2
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
.grid-stack {

View File

@@ -1,5 +1,5 @@
/*!
* GridStack 12.3.2
* GridStack 12.4.2
* https://gridstackjs.com/
*
* Copyright (c) 2021-2025 Alain Dumesny
@@ -798,5 +798,4 @@ export declare class GridStack {
prepareDragDrop(el: GridItemHTMLElement, force?: boolean): GridStack;
/** call given event callback on our main top-most grid (if we're nested) */
protected triggerEvent(event: Event, target: GridItemHTMLElement): void;
commit(): GridStack;
}

View File

@@ -1,12 +1,12 @@
/*!
* GridStack 12.3.2
* GridStack 12.4.2
* https://gridstackjs.com/
*
* Copyright (c) 2021-2025 Alain Dumesny
* see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE
*/
import { GridStackEngine } from './gridstack-engine';
import { Utils, obsolete } from './utils';
import { Utils } from './utils';
import { gridDefaults } from './types';
/*
* and include D&D by default
@@ -1376,6 +1376,8 @@ class GridStack {
if (o.maxRow !== undefined)
opts.maxRow = o.maxRow;
}
if (o.lazyLoad !== undefined)
opts.lazyLoad = o.lazyLoad;
if (o.children?.length)
this.load(o.children);
// TBD if we have a real need for these (more complex code)
@@ -1673,17 +1675,7 @@ class GridStack {
* alert('Not enough free space to place the widget');
* }
*/
willItFit(node) {
// support legacy call for now
if (arguments.length > 1) {
console.warn('gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon');
// eslint-disable-next-line prefer-rest-params
const a = arguments;
let i = 0, w = { x: a[i++], y: a[i++], w: a[i++], h: a[i++], autoPosition: a[i++] };
return this.willItFit(w);
}
return this.engine.willItFit(node);
}
willItFit(node) { return this.engine.willItFit(node); }
/** @internal */
_triggerChangeEvent() {
if (this.engine.batchMode)
@@ -1800,8 +1792,9 @@ class GridStack {
el.style.height = n.h > 1 ? `calc(${n.h} * var(--gs-cell-height))` : null;
}
// NOTE: those are technically not needed anymore (v12+) as we have CSS vars for everything, but some users depends on them to render item size using CSS
n.x > 0 ? el.setAttribute('gs-x', String(n.x)) : el.removeAttribute('gs-x');
n.y > 0 ? el.setAttribute('gs-y', String(n.y)) : el.removeAttribute('gs-y');
// ALways write x,y otherwise it could be autoPositioned incorrectly #3181
el.setAttribute('gs-x', String(n.x));
el.setAttribute('gs-y', String(n.y));
n.w > 1 ? el.setAttribute('gs-w', String(n.w)) : el.removeAttribute('gs-w');
n.h > 1 ? el.setAttribute('gs-h', String(n.h)) : el.removeAttribute('gs-h');
return this;
@@ -2851,8 +2844,6 @@ class GridStack {
this.engine.restoreInitial();
}
}
// legacy method removed
commit() { obsolete(this, this.batchUpdate(false), 'commit', 'batchUpdate', '5.2'); return this; }
}
/**
* callback to create the content of widgets so the app can control how to store and restore it
@@ -2867,6 +2858,6 @@ GridStack.Utils = Utils;
/** scoping so users can call new GridStack.Engine(12) for example */
GridStack.Engine = GridStackEngine;
/** @internal current version compiled in code */
GridStack.GDRev = '12.3.2';
GridStack.GDRev = '12.4.2';
export { GridStack };
//# sourceMappingURL=gridstack.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,358 +0,0 @@
import { GridStackEngine } from '../src/gridstack-engine';
describe('gridstack engine:', () => {
'use strict';
let e;
let ePriv; // cast engine for private vars access
let findNode = function (id) {
return e.nodes.find(n => n.id === id);
};
it('should exist setup function.', () => {
expect(GridStackEngine).not.toBeNull();
expect(typeof GridStackEngine).toBe('function');
});
describe('test constructor >', () => {
it('should be setup properly', () => {
ePriv = e = new GridStackEngine();
expect(e.column).toEqual(12);
expect(e.float).toEqual(false);
expect(e.maxRow).toEqual(undefined);
expect(e.nodes).toEqual([]);
expect(e.batchMode).toEqual(undefined);
expect(ePriv.onChange).toEqual(undefined);
});
it('should set params correctly.', () => {
let fkt = () => { };
let arr = [1, 2, 3];
ePriv = e = new GridStackEngine({ column: 1, onChange: fkt, float: true, maxRow: 2, nodes: arr });
expect(e.column).toEqual(1);
expect(e.float).toBe(true);
expect(e.maxRow).toEqual(2);
expect(e.nodes).toEqual(arr);
expect(e.batchMode).toEqual(undefined);
expect(ePriv.onChange).toEqual(fkt);
});
});
describe('batch update', () => {
it('should set float and batchMode when calling batchUpdate.', () => {
ePriv = e = new GridStackEngine({ float: true });
e.batchUpdate();
expect(e.float).toBe(true);
expect(e.batchMode).toBe(true);
});
});
describe('test prepareNode >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
});
it('should prepare a node', () => {
expect(e.prepareNode({}, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ x: 10 }, false)).toEqual(expect.objectContaining({ x: 10, y: 0, h: 1 }));
expect(e.prepareNode({ x: -10 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ y: 10 }, false)).toEqual(expect.objectContaining({ x: 0, y: 10, h: 1 }));
expect(e.prepareNode({ y: -10 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ w: 3 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, w: 3, h: 1 }));
expect(e.prepareNode({ w: 100 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, w: 12, h: 1 }));
expect(e.prepareNode({ w: 0 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ w: -190 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ h: 3 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 3 }));
expect(e.prepareNode({ h: 0 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ h: -10 }, false)).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(e.prepareNode({ x: 4, w: 10 }, false)).toEqual(expect.objectContaining({ x: 2, y: 0, w: 10, h: 1 }));
expect(e.prepareNode({ x: 4, w: 10 }, true)).toEqual(expect.objectContaining({ x: 4, y: 0, w: 8, h: 1 }));
});
});
describe('sorting of nodes >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
e.nodes = [{ x: 7, y: 0 }, { x: 4, y: 4 }, { x: 9, y: 0 }, { x: 0, y: 1 }];
});
it('should sort ascending with 12 columns.', () => {
e.sortNodes(1);
expect(e.nodes).toEqual([{ x: 7, y: 0 }, { x: 9, y: 0 }, { x: 0, y: 1 }, { x: 4, y: 4 }]);
});
it('should sort descending with 12 columns.', () => {
e.sortNodes(-1);
expect(e.nodes).toEqual([{ x: 4, y: 4 }, { x: 0, y: 1 }, { x: 9, y: 0 }, { x: 7, y: 0 }]);
});
it('should sort ascending without columns.', () => {
ePriv.column = undefined;
e.sortNodes(1);
expect(e.nodes).toEqual([{ x: 7, y: 0 }, { x: 9, y: 0 }, { x: 0, y: 1 }, { x: 4, y: 4 }]);
});
it('should sort descending without columns.', () => {
ePriv.column = undefined;
e.sortNodes(-1);
expect(e.nodes).toEqual([{ x: 4, y: 4 }, { x: 0, y: 1 }, { x: 9, y: 0 }, { x: 7, y: 0 }]);
});
});
describe('test isAreaEmpty >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine({ float: true });
e.nodes = [
e.prepareNode({ x: 3, y: 2, w: 3, h: 2 })
];
});
it('should be true', () => {
expect(e.isAreaEmpty(0, 0, 3, 2)).toEqual(true);
expect(e.isAreaEmpty(3, 4, 3, 2)).toEqual(true);
});
it('should be false', () => {
expect(e.isAreaEmpty(1, 1, 3, 2)).toEqual(false);
expect(e.isAreaEmpty(2, 3, 3, 2)).toEqual(false);
});
});
describe('test cleanNodes/getDirtyNodes >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine({ float: true });
e.nodes = [
e.prepareNode({ x: 0, y: 0, id: '1', _dirty: true }),
e.prepareNode({ x: 3, y: 2, w: 3, h: 2, id: '2', _dirty: true }),
e.prepareNode({ x: 3, y: 7, w: 3, h: 2, id: '3' })
];
});
beforeEach(() => {
delete ePriv.batchMode;
});
it('should return all dirty nodes', () => {
let nodes = e.getDirtyNodes();
expect(nodes.length).toEqual(2);
expect(nodes[0].id).toEqual('1');
expect(nodes[1].id).toEqual('2');
});
it('should\'n clean nodes if batchMode true', () => {
e.batchMode = true;
e.cleanNodes();
expect(e.getDirtyNodes().length).toBeGreaterThan(0);
});
it('should clean all dirty nodes', () => {
e.cleanNodes();
expect(e.getDirtyNodes().length).toEqual(0);
});
});
describe('test batchUpdate/commit >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
});
it('should work on not float grids', () => {
expect(e.float).toEqual(false);
e.batchUpdate();
e.batchUpdate(); // double for code coverage
expect(e.batchMode).toBe(true);
expect(e.float).toEqual(true);
e.batchUpdate(false);
e.batchUpdate(false);
expect(e.batchMode).not.toBe(true);
expect(e.float).not.toBe(true);
});
it('should work on float grids', () => {
e.float = true;
e.batchUpdate();
expect(e.batchMode).toBe(true);
expect(e.float).toEqual(true);
e.batchUpdate(false);
expect(e.batchMode).not.toBe(true);
expect(e.float).toEqual(true);
});
});
describe('test batchUpdate/commit >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine({ float: true });
});
it('should work on float grids', () => {
expect(e.float).toEqual(true);
e.batchUpdate();
expect(e.batchMode).toBe(true);
expect(e.float).toEqual(true);
e.batchUpdate(false);
expect(e.batchMode).not.toBe(true);
expect(e.float).toEqual(true);
});
});
describe('test _notify >', () => {
let spy;
beforeEach(() => {
spy = {
callback: () => { }
};
vi.spyOn(spy, 'callback');
ePriv = e = new GridStackEngine({ float: true, onChange: spy.callback });
e.nodes = [
e.prepareNode({ x: 0, y: 0, id: '1', _dirty: true }),
e.prepareNode({ x: 3, y: 2, w: 3, h: 2, id: '2', _dirty: true }),
e.prepareNode({ x: 3, y: 7, w: 3, h: 2, id: '3' })
];
});
it('should\'n be called if batchMode true', () => {
e.batchMode = true;
ePriv._notify();
expect(spy.callback).not.toHaveBeenCalled();
});
it('should by called with dirty nodes', () => {
ePriv._notify();
expect(spy.callback).toHaveBeenCalledWith([e.nodes[0], e.nodes[1]]);
});
it('should by called with extra passed node to be removed', () => {
let n1 = { id: -1 };
ePriv._notify([n1]);
expect(spy.callback).toHaveBeenCalledWith([n1, e.nodes[0], e.nodes[1]]);
});
});
describe('test _packNodes >', () => {
describe('using float:false mode >', () => {
beforeEach(() => {
ePriv = e = new GridStackEngine({ float: false });
});
it('shouldn\'t pack one node with y coord eq 0', () => {
e.nodes = [
e.prepareNode({ x: 0, y: 0, w: 1, h: 1, id: '1' }),
];
ePriv._packNodes();
expect(findNode('1')).toEqual(expect.objectContaining({ x: 0, y: 0, h: 1 }));
expect(findNode('1')._dirty).toBeFalsy();
});
it('should pack one node correctly', () => {
e.nodes = [
e.prepareNode({ x: 0, y: 1, w: 1, h: 1, id: '1' }),
];
ePriv._packNodes();
expect(findNode('1')).toEqual(expect.objectContaining({ x: 0, y: 0, _dirty: true }));
});
it('should pack nodes correctly', () => {
e.nodes = [
e.prepareNode({ x: 0, y: 1, w: 1, h: 1, id: '1' }),
e.prepareNode({ x: 0, y: 5, w: 1, h: 1, id: '2' }),
];
ePriv._packNodes();
expect(findNode('1')).toEqual(expect.objectContaining({ x: 0, y: 0, _dirty: true }));
expect(findNode('2')).toEqual(expect.objectContaining({ x: 0, y: 1, _dirty: true }));
});
it('should pack reverse nodes correctly', () => {
e.nodes = [
e.prepareNode({ x: 0, y: 5, w: 1, h: 1, id: '1' }),
e.prepareNode({ x: 0, y: 1, w: 1, h: 1, id: '2' }),
];
ePriv._packNodes();
expect(findNode('2')).toEqual(expect.objectContaining({ x: 0, y: 0, _dirty: true }));
expect(findNode('1')).toEqual(expect.objectContaining({ x: 0, y: 1, _dirty: true }));
});
it('should respect locked nodes', () => {
e.nodes = [
e.prepareNode({ x: 0, y: 1, w: 1, h: 1, id: '1', locked: true }),
e.prepareNode({ x: 0, y: 5, w: 1, h: 1, id: '2' }),
];
ePriv._packNodes();
expect(findNode('1')).toEqual(expect.objectContaining({ x: 0, y: 1, h: 1 }));
expect(findNode('1')._dirty).toBeFalsy();
expect(findNode('2')).toEqual(expect.objectContaining({ x: 0, y: 2, _dirty: true }));
});
});
});
describe('test changedPos >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
});
it('should return true for changed x', () => {
let widget = { x: 1, y: 2, w: 3, h: 4 };
expect(e.changedPosConstrain(widget, { x: 2, y: 2 })).toEqual(true);
});
it('should return true for changed y', () => {
let widget = { x: 1, y: 2, w: 3, h: 4 };
expect(e.changedPosConstrain(widget, { x: 1, y: 1 })).toEqual(true);
});
it('should return true for changed width', () => {
let widget = { x: 1, y: 2, w: 3, h: 4 };
expect(e.changedPosConstrain(widget, { x: 2, y: 2, w: 4, h: 4 })).toEqual(true);
});
it('should return true for changed height', () => {
let widget = { x: 1, y: 2, w: 3, h: 4 };
expect(e.changedPosConstrain(widget, { x: 1, y: 2, w: 3, h: 3 })).toEqual(true);
});
it('should return false for unchanged position', () => {
let widget = { x: 1, y: 2, w: 3, h: 4 };
expect(e.changedPosConstrain(widget, { x: 1, y: 2, w: 3, h: 4 })).toEqual(false);
});
});
describe('test locked widget >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
});
it('should add widgets around locked one', () => {
let nodes = [
{ x: 0, y: 1, w: 12, h: 1, locked: true, noMove: true, noResize: true, id: '0' },
{ x: 1, y: 0, w: 2, h: 3, id: '1' }
];
// add locked item
e.addNode(nodes[0]);
expect(findNode('0')).toEqual(expect.objectContaining({ x: 0, y: 1, w: 12, h: 1, locked: true }));
// add item that moves past locked one
e.addNode(nodes[1]);
expect(findNode('0')).toEqual(expect.objectContaining({ x: 0, y: 1, w: 12, h: 1, locked: true }));
expect(findNode('1')).toEqual(expect.objectContaining({ x: 1, y: 2, h: 3 }));
// locked item can still be moved directly (what user does)
let node0 = findNode('0');
expect(e.moveNode(node0, { y: 6 })).toEqual(true);
expect(findNode('0')).toEqual(expect.objectContaining({ x: 0, y: 6, h: 1, locked: true }));
// but moves regular one past it
let node1 = findNode('1');
expect(e.moveNode(node1, { x: 6, y: 6 })).toEqual(true);
expect(node1).toEqual(expect.objectContaining({ x: 6, y: 7, w: 2, h: 3 }));
// but moves regular one before (gravity ON)
e.float = false;
expect(e.moveNode(node1, { x: 7, y: 3 })).toEqual(true);
expect(node1).toEqual(expect.objectContaining({ x: 7, y: 0, w: 2, h: 3 }));
// but moves regular one before (gravity OFF)
e.float = true;
expect(e.moveNode(node1, { x: 7, y: 3 })).toEqual(true);
expect(node1).toEqual(expect.objectContaining({ x: 7, y: 3, w: 2, h: 3 }));
});
});
describe('test columnChanged >', () => {
beforeAll(() => {
});
it('12 to 1 and back', () => {
ePriv = e = new GridStackEngine({ column: 12 });
// Add two side-by-side components 6+6 = 12 columns
const left = e.addNode({ x: 0, y: 0, w: 6, h: 1, id: 'left' });
const right = e.addNode({ x: 6, y: 0, w: 6, h: 1, id: 'right' });
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 6, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 6, y: 0, w: 6, h: 1 }));
// Resize to 1 column
e.column = 1;
e.columnChanged(12, 1);
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 1, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 0, y: 1, w: 1, h: 1 }));
// Resize back to 12 column
e.column = 12;
e.columnChanged(1, 12);
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 6, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 6, y: 0, w: 6, h: 1 }));
});
it('24 column to 1 and back', () => {
ePriv = e = new GridStackEngine({ column: 24 });
// Add two side-by-side components 12+12 = 24 columns
const left = e.addNode({ x: 0, y: 0, w: 12, h: 1, id: 'left' });
const right = e.addNode({ x: 12, y: 0, w: 12, h: 1, id: 'right' });
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 12, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 12, y: 0, w: 12, h: 1 }));
// Resize to 1 column
e.column = 1;
e.columnChanged(24, 1);
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 1, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 0, y: 1, w: 1, h: 1 }));
// Resize back to 24 column
e.column = 24;
e.columnChanged(1, 24);
expect(left).toEqual(expect.objectContaining({ x: 0, y: 0, w: 12, h: 1 }));
expect(right).toEqual(expect.objectContaining({ x: 12, y: 0, w: 12, h: 1 }));
});
});
describe('test compact >', () => {
beforeAll(() => {
ePriv = e = new GridStackEngine();
});
it('do nothing', () => {
e.compact();
});
});
});
//# sourceMappingURL=gridstack-engine-spec.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,171 +0,0 @@
import { GridStack } from '../../src/gridstack';
// Integration tests for GridStack HTML scenarios
// These test actual GridStack behavior with DOM manipulation
describe('GridStack Integration Tests', () => {
beforeEach(() => {
// // Clean up DOM before each test
// document.body.innerHTML = '';
// // Add basic CSS for GridStack to function properly
// const style = document.createElement('style');
// style.textContent = `
// .grid-stack { position: relative; }
// .grid-stack-item { position: absolute; }
// .grid-stack-item-content { width: 100%; height: 100%; }
// `;
// document.head.appendChild(style);
});
afterEach(() => {
// // Clean up any GridStack instances
// GridStack.removeAll;
// // Clean up added styles
// const styles = document.head.querySelectorAll('style');
// styles.forEach(style => style.remove());
});
describe('Auto-positioning with no x,y coordinates', () => {
it('should position items in order 5,1,2,4,3 based on their constraints', () => {
// Create the HTML structure from the test file
document.body.innerHTML = `
<div class="grid-stack">
<div class="grid-stack-item upper" gs-w="2" gs-h="2" gs-id="1">
<div class="grid-stack-item-content">item 1</div>
</div>
<div class="grid-stack-item" gs-w="3" gs-h="2" gs-id="2">
<div class="grid-stack-item-content">item 2</div>
</div>
<div class="grid-stack-item" gs-w="9" gs-h="1" gs-id="3">
<div class="grid-stack-item-content">item 3 too big to fit, so next row</div>
</div>
<div class="grid-stack-item" gs-w="3" gs-h="1" gs-id="4">
<div class="grid-stack-item-content">item 4</div>
</div>
<div class="grid-stack-item" gs-x="1" gs-y="1" gs-w="1" gs-h="1" gs-id="5">
<div class="grid-stack-item-content">item 5 first</div>
</div>
</div>
`;
// Initialize GridStack with same options as test
const options = {
cellHeight: 80,
margin: 5,
float: true
};
const grid = GridStack.init(options);
// Get all nodes and their positions
const nodes = grid.engine.nodes;
expect(nodes).toHaveLength(5);
// Item 5 should be positioned (has explicit x=1, y=1 in HTML)
const item5 = nodes.find(n => n.id === '5');
expect(item5).toBeDefined();
expect(item5.w).toBe(1);
expect(item5.h).toBe(1);
// Item 1 should be positioned next (2x2)
const item1 = nodes.find(n => n.id === '1');
expect(item1).toBeDefined();
expect(item1.w).toBe(2);
expect(item1.h).toBe(2);
// Item 2 should be positioned (3x2)
const item2 = nodes.find(n => n.id === '2');
expect(item2).toBeDefined();
expect(item2.w).toBe(3);
expect(item2.h).toBe(2);
// Item 4 should be positioned (3x1)
const item4 = nodes.find(n => n.id === '4');
expect(item4).toBeDefined();
expect(item4.w).toBe(3);
expect(item4.h).toBe(1);
// Item 3 should be on next row (too big to fit - 9x1)
const item3 = nodes.find(n => n.id === '3');
expect(item3).toBeDefined();
expect(item3.w).toBe(9);
expect(item3.h).toBe(1);
// Verify all items are positioned (have valid coordinates)
nodes.forEach(node => {
expect(node.x).toBeGreaterThanOrEqual(0);
expect(node.y).toBeGreaterThanOrEqual(0);
expect(node.w).toBeGreaterThan(0);
expect(node.h).toBeGreaterThan(0);
});
});
});
describe('Grid initialization and basic functionality', () => {
it('should initialize GridStack with items and maintain data integrity', () => {
document.body.innerHTML = `
<div class="grid-stack">
<div class="grid-stack-item" gs-x="0" gs-y="0" gs-w="4" gs-h="2" gs-id="item1">
<div class="grid-stack-item-content">Item 1</div>
</div>
<div class="grid-stack-item" gs-x="4" gs-y="0" gs-w="4" gs-h="4" gs-id="item2">
<div class="grid-stack-item-content">Item 2</div>
</div>
</div>
`;
const grid = GridStack.init();
expect(grid).toBeDefined();
expect(grid.engine.nodes).toHaveLength(2);
const item1 = grid.engine.nodes.find(n => n.id === 'item1');
const item2 = grid.engine.nodes.find(n => n.id === 'item2');
expect(item1).toEqual(expect.objectContaining({
x: 0, y: 0, w: 4, h: 2, id: 'item1'
}));
expect(item2).toEqual(expect.objectContaining({
x: 4, y: 0, w: 4, h: 4, id: 'item2'
}));
});
it('should handle empty grid initialization', () => {
document.body.innerHTML = '<div class="grid-stack"></div>';
const grid = GridStack.init();
expect(grid).toBeDefined();
expect(grid.engine.nodes).toHaveLength(0);
});
it('should add widgets programmatically', () => {
document.body.innerHTML = '<div class="grid-stack"></div>';
const grid = GridStack.init();
const addedEl = grid.addWidget({
x: 0, y: 0, w: 2, h: 2, id: 'new-widget'
});
expect(addedEl).toBeDefined();
expect(grid.engine.nodes).toHaveLength(1);
// Check that the widget was added with valid properties
const node = grid.engine.nodes[0];
expect(node.x).toBe(0);
expect(node.y).toBe(0);
// Note: w and h might default to 1x1 if not explicitly set in the HTML attributes
});
});
describe('Layout and positioning validation', () => {
it('should respect minRow constraints', () => {
document.body.innerHTML = '<div class="grid-stack"></div>';
const grid = GridStack.init({ minRow: 3 });
// Even with no items, grid should maintain minimum rows
expect(grid.getRow()).toBeGreaterThanOrEqual(3);
});
it('should handle widget collision detection', () => {
document.body.innerHTML = `
<div class="grid-stack">
<div class="grid-stack-item" gs-x="0" gs-y="0" gs-w="2" gs-h="2" gs-id="item1">
<div class="grid-stack-item-content">Item 1</div>
</div>
</div>
`;
const grid = GridStack.init();
// Try to add overlapping widget
const widgetEl = grid.addWidget({
x: 1, y: 1, w: 2, h: 2, id: 'overlap'
});
expect(widgetEl).toBeDefined();
expect(grid.engine.nodes).toHaveLength(2);
// Verify that items don't actually overlap (GridStack should handle collision)
// Just verify we have 2 nodes without overlap testing since the API changed
const nodes = grid.engine.nodes;
expect(nodes).toHaveLength(2);
// All nodes should have valid positions
nodes.forEach(node => {
expect(node.x).toBeGreaterThanOrEqual(0);
expect(node.y).toBeGreaterThanOrEqual(0);
expect(node.w).toBeGreaterThan(0);
expect(node.h).toBeGreaterThan(0);
});
});
});
});
//# sourceMappingURL=gridstack-integration.spec.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,100 +0,0 @@
import { GridStack } from '../src/gridstack';
describe('regression >', () => {
'use strict';
let grid;
let findEl = function (id) {
return grid.engine.nodes.find(n => n.id === id).el;
};
let findSubEl = function (id, index = 0) {
return grid.engine.nodes[index].subGrid?.engine.nodes.find(n => n.id === id).el;
};
// empty grid
let gridstackEmptyHTML = '<div style="width: 800px; height: 600px" id="gs-cont">' +
' <div class="grid-stack"></div>' +
'</div>';
describe('2492 load() twice >', () => {
beforeEach(() => {
document.body.insertAdjacentHTML('afterbegin', gridstackEmptyHTML);
});
afterEach(() => {
document.body.removeChild(document.getElementById('gs-cont'));
});
it('', () => {
let items = [
{ x: 0, y: 0, w: 2, content: '0 wide' },
{ x: 1, y: 0, content: '1 over' },
{ x: 2, y: 1, content: '2 float' },
];
let count = 0;
items.forEach(n => n.id = String(count++));
grid = GridStack.init({ cellHeight: 70, margin: 5 }).load(items);
let el0 = findEl('0');
let el1 = findEl('1');
let el2 = findEl('2');
expect(el0.getAttribute('gs-x')).toBe(null);
expect(el0.getAttribute('gs-y')).toBe(null);
expect(el0.children[0].innerHTML).toBe(items[0].content);
expect(parseInt(el1.getAttribute('gs-x'))).toBe(1);
expect(parseInt(el1.getAttribute('gs-y'))).toBe(1);
expect(parseInt(el2.getAttribute('gs-x'))).toBe(2);
expect(el2.getAttribute('gs-y')).toBe(null);
// loading with changed content should be same positions
items.forEach(n => n.content += '*');
grid.load(items);
expect(el0.getAttribute('gs-x')).toBe(null);
expect(el0.getAttribute('gs-y')).toBe(null);
expect(el0.children[0].innerHTML).toBe(items[0].content);
expect(parseInt(el1.getAttribute('gs-x'))).toBe(1);
expect(parseInt(el1.getAttribute('gs-y'))).toBe(1);
expect(parseInt(el2.getAttribute('gs-x'))).toBe(2);
expect(el2.getAttribute('gs-y')).toBe(null);
});
});
describe('2865 nested grid resize >', () => {
beforeEach(() => {
document.body.insertAdjacentHTML('afterbegin', gridstackEmptyHTML);
});
afterEach(() => {
document.body.removeChild(document.getElementById('gs-cont'));
});
it('', () => {
let children = [{}, {}, {}];
let items = [
{ x: 0, y: 0, w: 3, h: 5, sizeToContent: true, subGridOpts: { children, column: 'auto' } }
];
let count = 0;
[...items, ...children].forEach(n => n.id = String(count++));
grid = GridStack.init({ cellHeight: 70, margin: 5, children: items });
let nested = findEl('0');
let el1 = findSubEl('1');
let el2 = findSubEl('2');
let el3 = findSubEl('3');
expect(nested.getAttribute('gs-x')).toBe(null);
expect(nested.getAttribute('gs-y')).toBe(null);
expect(parseInt(nested.getAttribute('gs-w'))).toBe(3);
// TODO: sizeToContent doesn't seem to be called in headless mode ??? works in browser.
// expect(nested.getAttribute('gs-h')).toBe(null); // sizeToContent 5 -> 1 which is null
expect(el1.getAttribute('gs-x')).toBe(null);
expect(el1.getAttribute('gs-y')).toBe(null);
expect(parseInt(el2.getAttribute('gs-x'))).toBe(1);
expect(el2.getAttribute('gs-y')).toBe(null);
expect(parseInt(el3.getAttribute('gs-x'))).toBe(2);
expect(el3.getAttribute('gs-y')).toBe(null);
// now resize the nested grid to 2 -> should reflow el3
grid.update(nested, { w: 2 });
expect(nested.getAttribute('gs-x')).toBe(null);
expect(nested.getAttribute('gs-y')).toBe(null);
expect(parseInt(nested.getAttribute('gs-w'))).toBe(2);
// TODO: sizeToContent doesn't seem to be called in headless mode ??? works in browser.
// expect(parseInt(nested.getAttribute('gs-h'))).toBe(2);
expect(el1.getAttribute('gs-x')).toBe(null);
expect(el1.getAttribute('gs-y')).toBe(null);
expect(parseInt(el2.getAttribute('gs-x'))).toBe(1);
expect(el2.getAttribute('gs-y')).toBe(null);
// 3rd item pushed to next row
expect(el3.getAttribute('gs-x')).toBe(null);
expect(parseInt(el3.getAttribute('gs-y'))).toBe(1);
});
});
});
//# sourceMappingURL=regression-spec.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,243 +0,0 @@
import { Utils } from '../src/utils';
describe('gridstack utils', () => {
describe('setup of utils', () => {
it('should set gridstack Utils.', () => {
let utils = Utils;
expect(utils).not.toBeNull();
expect(typeof utils).toBe('function');
});
});
describe('test toBool', () => {
it('should return booleans.', () => {
expect(Utils.toBool(true)).toEqual(true);
expect(Utils.toBool(false)).toEqual(false);
});
it('should work with integer.', () => {
expect(Utils.toBool(1)).toEqual(true);
expect(Utils.toBool(0)).toEqual(false);
});
it('should work with Strings.', () => {
expect(Utils.toBool('')).toEqual(false);
expect(Utils.toBool('0')).toEqual(false);
expect(Utils.toBool('no')).toEqual(false);
expect(Utils.toBool('false')).toEqual(false);
expect(Utils.toBool('yes')).toEqual(true);
expect(Utils.toBool('yadda')).toEqual(true);
});
});
describe('test isIntercepted', () => {
let src = { x: 3, y: 2, w: 3, h: 2 };
it('should intercept.', () => {
expect(Utils.isIntercepted(src, { x: 0, y: 0, w: 4, h: 3 })).toEqual(true);
expect(Utils.isIntercepted(src, { x: 0, y: 0, w: 40, h: 30 })).toEqual(true);
expect(Utils.isIntercepted(src, { x: 3, y: 2, w: 3, h: 2 })).toEqual(true);
expect(Utils.isIntercepted(src, { x: 5, y: 3, w: 3, h: 2 })).toEqual(true);
});
it('shouldn\'t intercept.', () => {
expect(Utils.isIntercepted(src, { x: 0, y: 0, w: 3, h: 2 })).toEqual(false);
expect(Utils.isIntercepted(src, { x: 0, y: 0, w: 13, h: 2 })).toEqual(false);
expect(Utils.isIntercepted(src, { x: 1, y: 4, w: 13, h: 2 })).toEqual(false);
expect(Utils.isIntercepted(src, { x: 0, y: 3, w: 3, h: 2 })).toEqual(false);
expect(Utils.isIntercepted(src, { x: 6, y: 3, w: 3, h: 2 })).toEqual(false);
});
});
describe('test parseHeight', () => {
it('should parse height value', () => {
expect(Utils.parseHeight(12)).toEqual(expect.objectContaining({ h: 12, unit: 'px' }));
expect(Utils.parseHeight('12px')).toEqual(expect.objectContaining({ h: 12, unit: 'px' }));
expect(Utils.parseHeight('12.3px')).toEqual(expect.objectContaining({ h: 12.3, unit: 'px' }));
expect(Utils.parseHeight('12.3em')).toEqual(expect.objectContaining({ h: 12.3, unit: 'em' }));
expect(Utils.parseHeight('12.3rem')).toEqual(expect.objectContaining({ h: 12.3, unit: 'rem' }));
expect(Utils.parseHeight('12.3vh')).toEqual(expect.objectContaining({ h: 12.3, unit: 'vh' }));
expect(Utils.parseHeight('12.3vw')).toEqual(expect.objectContaining({ h: 12.3, unit: 'vw' }));
expect(Utils.parseHeight('12.3%')).toEqual(expect.objectContaining({ h: 12.3, unit: '%' }));
expect(Utils.parseHeight('12.5cm')).toEqual(expect.objectContaining({ h: 12.5, unit: 'cm' }));
expect(Utils.parseHeight('12.5mm')).toEqual(expect.objectContaining({ h: 12.5, unit: 'mm' }));
expect(Utils.parseHeight('12.5')).toEqual(expect.objectContaining({ h: 12.5, unit: 'px' }));
expect(() => { Utils.parseHeight('12.5 df'); }).toThrow('Invalid height val = 12.5 df');
});
it('should parse negative height value', () => {
expect(Utils.parseHeight(-12)).toEqual(expect.objectContaining({ h: -12, unit: 'px' }));
expect(Utils.parseHeight('-12px')).toEqual(expect.objectContaining({ h: -12, unit: 'px' }));
expect(Utils.parseHeight('-12.3px')).toEqual(expect.objectContaining({ h: -12.3, unit: 'px' }));
expect(Utils.parseHeight('-12.3em')).toEqual(expect.objectContaining({ h: -12.3, unit: 'em' }));
expect(Utils.parseHeight('-12.3rem')).toEqual(expect.objectContaining({ h: -12.3, unit: 'rem' }));
expect(Utils.parseHeight('-12.3vh')).toEqual(expect.objectContaining({ h: -12.3, unit: 'vh' }));
expect(Utils.parseHeight('-12.3vw')).toEqual(expect.objectContaining({ h: -12.3, unit: 'vw' }));
expect(Utils.parseHeight('-12.3%')).toEqual(expect.objectContaining({ h: -12.3, unit: '%' }));
expect(Utils.parseHeight('-12.3cm')).toEqual(expect.objectContaining({ h: -12.3, unit: 'cm' }));
expect(Utils.parseHeight('-12.3mm')).toEqual(expect.objectContaining({ h: -12.3, unit: 'mm' }));
expect(Utils.parseHeight('-12.5')).toEqual(expect.objectContaining({ h: -12.5, unit: 'px' }));
expect(() => { Utils.parseHeight('-12.5 df'); }).toThrow('Invalid height val = -12.5 df');
});
});
describe('test defaults', () => {
it('should assign missing field or undefined', () => {
let src = {};
expect(src).toEqual({});
expect(Utils.defaults(src, { x: 1, y: 2 })).toEqual({ x: 1, y: 2 });
expect(Utils.defaults(src, { x: 10 })).toEqual({ x: 1, y: 2 });
src.w = undefined;
expect(src).toEqual({ x: 1, y: 2, w: undefined });
expect(Utils.defaults(src, { x: 10, w: 3 })).toEqual({ x: 1, y: 2, w: 3 });
expect(Utils.defaults(src, { h: undefined })).toEqual({ x: 1, y: 2, w: 3, h: undefined });
src = { x: 1, y: 2, sub: { foo: 1, two: 2 } };
expect(src).toEqual({ x: 1, y: 2, sub: { foo: 1, two: 2 } });
expect(Utils.defaults(src, { x: 10, w: 3 })).toEqual({ x: 1, y: 2, w: 3, sub: { foo: 1, two: 2 } });
expect(Utils.defaults(src, { sub: { three: 3 } })).toEqual({ x: 1, y: 2, w: 3, sub: { foo: 1, two: 2, three: 3 } });
});
});
describe('removePositioningStyles', () => {
it('should remove styles', () => {
let doc = document.implementation.createHTMLDocument();
doc.body.innerHTML = '<div style="position: absolute; left: 1; top: 2; w: 3; h: 4"></div>';
let el = doc.body.children[0];
expect(el.style.position).toEqual('absolute');
// expect(el.style.left).toEqual('1'); // not working!
Utils.removePositioningStyles(el);
expect(el.style.position).toEqual('');
// bogus test
expect(Utils.getScrollElement(el)).not.toBe(null);
// bogus test
Utils.updateScrollPosition(el, { top: 20 }, 10);
});
});
describe('clone', () => {
const a = { first: 1, second: 'text' };
const b = { first: 1, second: { third: 3 } };
const c = { first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } };
it('Should have the same values', () => {
const z = Utils.clone(a);
expect(z).toEqual({ first: 1, second: 'text' });
});
it('Should have 2 in first key, and original unchanged', () => {
const z = Utils.clone(a);
z.first = 2;
expect(a).toEqual({ first: 1, second: 'text' });
expect(z).toEqual({ first: 2, second: 'text' });
});
it('Should have new string in second key, and original unchanged', () => {
const z = Utils.clone(a);
z.second = 2;
expect(a).toEqual({ first: 1, second: 'text' });
expect(z).toEqual({ first: 1, second: 2 });
});
it('new string in both cases - use cloneDeep instead', () => {
const z = Utils.clone(b);
z.second.third = 'share';
expect(b).toEqual({ first: 1, second: { third: 'share' } });
expect(z).toEqual({ first: 1, second: { third: 'share' } });
});
it('Array Should match', () => {
const z = Utils.clone(c);
expect(c).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
expect(z).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
});
it('Array[0] changed in both cases - use cloneDeep instead', () => {
const z = Utils.clone(c);
z.second[0] = 0;
expect(c).toEqual({ first: 1, second: [0, 2, 3], third: { fourth: { fifth: 5 } } });
expect(z).toEqual({ first: 1, second: [0, 2, 3], third: { fourth: { fifth: 5 } } });
});
it('fifth changed in both cases - use cloneDeep instead', () => {
const z = Utils.clone(c);
z.third.fourth.fifth = 'share';
expect(c).toEqual({ first: 1, second: [0, 2, 3], third: { fourth: { fifth: 'share' } } });
expect(z).toEqual({ first: 1, second: [0, 2, 3], third: { fourth: { fifth: 'share' } } });
});
});
describe('cloneDeep', () => {
// reset our test cases
const a = { first: 1, second: 'text' };
const b = { first: 1, second: { third: 3 } };
const c = { first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } };
const d = { first: [1, [2, 3], ['four', 'five', 'six']] };
const e = { first: 1, __skip: { second: 2 } };
const f = { first: 1, _dontskip: { second: 2 } };
it('Should have the same values', () => {
const z = Utils.cloneDeep(a);
expect(z).toEqual({ first: 1, second: 'text' });
});
it('Should have 2 in first key, and original unchanged', () => {
const z = Utils.cloneDeep(a);
z.first = 2;
expect(a).toEqual({ first: 1, second: 'text' });
expect(z).toEqual({ first: 2, second: 'text' });
});
it('Should have new string in second key, and original unchanged', () => {
const z = Utils.cloneDeep(a);
z.second = 2;
expect(a).toEqual({ first: 1, second: 'text' });
expect(z).toEqual({ first: 1, second: 2 });
});
it('Should have new string nested object, and original unchanged', () => {
const z = Utils.cloneDeep(b);
z.second.third = 'diff';
expect(b).toEqual({ first: 1, second: { third: 3 } });
expect(z).toEqual({ first: 1, second: { third: 'diff' } });
});
it('Array Should match', () => {
const z = Utils.cloneDeep(c);
expect(c).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
expect(z).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
});
it('Array[0] changed in z only', () => {
const z = Utils.cloneDeep(c);
z.second[0] = 0;
expect(c).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
expect(z).toEqual({ first: 1, second: [0, 2, 3], third: { fourth: { fifth: 5 } } });
});
it('nested firth element changed only in z', () => {
const z = Utils.cloneDeep(c);
z.third.fourth.fifth = 'diff';
expect(c).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 5 } } });
expect(z).toEqual({ first: 1, second: [1, 2, 3], third: { fourth: { fifth: 'diff' } } });
});
it('nested array only has one item changed', () => {
const z = Utils.cloneDeep(d);
z.first[1] = 'two';
z.first[2][2] = 6;
expect(d).toEqual({ first: [1, [2, 3], ['four', 'five', 'six']] });
expect(z).toEqual({ first: [1, 'two', ['four', 'five', 6]] });
});
it('skip __ items so it mods both instance', () => {
const z = Utils.cloneDeep(e);
z.__skip.second = 'two';
expect(e).toEqual({ first: 1, __skip: { second: 'two' } }); // TODO support clone deep of function workaround
expect(z).toEqual({ first: 1, __skip: { second: 'two' } });
});
it('correctly copy _ item', () => {
const z = Utils.cloneDeep(f);
z._dontskip.second = 'two';
expect(f).toEqual({ first: 1, _dontskip: { second: 2 } });
expect(z).toEqual({ first: 1, _dontskip: { second: 'two' } });
});
});
describe('removeInternalAndSame', () => {
it('should remove internal and same', () => {
const a = { first: 1, second: 'text', _skip: { second: 2 }, arr: [1, 'second', 3] };
const b = { first: 1, second: 'text' };
Utils.removeInternalAndSame(a, b);
expect(a).toEqual({ arr: [1, 'second', 3] });
});
it('should not remove items in an array', () => {
const a = { arr: [1, 2, 3] };
const b = { arr: [1, 3] };
Utils.removeInternalAndSame(a, b);
expect(a).toEqual({ arr: [1, 2, 3] });
});
it('should remove nested object, and make empty', () => {
const a = { obj1: { first: 1, nested: { second: 2 } }, obj2: { first: 1, second: 2 } };
const b = { obj1: { first: 1, nested: { second: 2 } }, obj2: { first: 1, second: 2 } };
Utils.removeInternalAndSame(a, b);
expect(a).toEqual({});
});
it('should remove nested object, and make empty - part 2', () => {
const a = { obj1: { first: 1, nested: { second: 2 } }, obj2: { first: 1, second: 2 } };
const b = { obj1: { first: 1 }, obj2: { first: 1, second: 2 } };
Utils.removeInternalAndSame(a, b);
expect(a).toEqual({ obj1: { nested: { second: 2 } } });
});
});
});
//# sourceMappingURL=utils-spec.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,69 +0,0 @@
/**
* dd-base-impl.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**
* Type for event callback functions used in drag & drop operations.
* Can return boolean to indicate if the event should continue propagation.
*/
export type EventCallback = (event: Event) => boolean | void;
/**
* Abstract base class for all drag & drop implementations.
* Provides common functionality for event handling, enable/disable state,
* and lifecycle management used by draggable, droppable, and resizable implementations.
*/
export declare abstract class DDBaseImplement {
/**
* Returns the current disabled state.
* Note: Use enable()/disable() methods to change state as other operations need to happen.
*/
get disabled(): boolean;
/**
* Register an event callback for the specified event.
*
* @param event - Event name to listen for
* @param callback - Function to call when event occurs
*/
on(event: string, callback: EventCallback): void;
/**
* Unregister an event callback for the specified event.
*
* @param event - Event name to stop listening for
*/
off(event: string): void;
/**
* Enable this drag & drop implementation.
* Subclasses should override to perform additional setup.
*/
enable(): void;
/**
* Disable this drag & drop implementation.
* Subclasses should override to perform additional cleanup.
*/
disable(): void;
/**
* Destroy this drag & drop implementation and clean up resources.
* Removes all event handlers and clears internal state.
*/
destroy(): void;
/**
* Trigger a registered event callback if one exists and the implementation is enabled.
*
* @param eventName - Name of the event to trigger
* @param event - DOM event object to pass to the callback
* @returns Result from the callback function, if any
*/
triggerEvent(eventName: string, event: Event): boolean | void;
}
/**
* Interface for HTML elements extended with drag & drop options.
* Used to associate DD configuration with DOM elements.
*/
export interface HTMLElementExtendOpt<T> {
/** The HTML element being extended */
el: HTMLElement;
/** The drag & drop options/configuration */
option: T;
/** Method to update the options and return the DD implementation */
updateOption(T: any): DDBaseImplement;
}

View File

@@ -1,70 +0,0 @@
/**
* dd-base-impl.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**
* Abstract base class for all drag & drop implementations.
* Provides common functionality for event handling, enable/disable state,
* and lifecycle management used by draggable, droppable, and resizable implementations.
*/
export class DDBaseImplement {
constructor() {
/** @internal */
this._eventRegister = {};
}
/**
* Returns the current disabled state.
* Note: Use enable()/disable() methods to change state as other operations need to happen.
*/
get disabled() { return this._disabled; }
/**
* Register an event callback for the specified event.
*
* @param event - Event name to listen for
* @param callback - Function to call when event occurs
*/
on(event, callback) {
this._eventRegister[event] = callback;
}
/**
* Unregister an event callback for the specified event.
*
* @param event - Event name to stop listening for
*/
off(event) {
delete this._eventRegister[event];
}
/**
* Enable this drag & drop implementation.
* Subclasses should override to perform additional setup.
*/
enable() {
this._disabled = false;
}
/**
* Disable this drag & drop implementation.
* Subclasses should override to perform additional cleanup.
*/
disable() {
this._disabled = true;
}
/**
* Destroy this drag & drop implementation and clean up resources.
* Removes all event handlers and clears internal state.
*/
destroy() {
delete this._eventRegister;
}
/**
* Trigger a registered event callback if one exists and the implementation is enabled.
*
* @param eventName - Name of the event to trigger
* @param event - DOM event object to pass to the callback
* @returns Result from the callback function, if any
*/
triggerEvent(eventName, event) {
if (!this.disabled && this._eventRegister && this._eventRegister[eventName])
return this._eventRegister[eventName](event);
}
}
//# sourceMappingURL=dd-base-impl.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"dd-base-impl.js","sourceRoot":"","sources":["../../src/dd-base-impl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;;GAIG;AACH,MAAM,OAAgB,eAAe;IAArC;QASE,gBAAgB;QACN,mBAAc,GAEpB,EAAE,CAAC;IAwDT,CAAC;IAnEC;;;OAGG;IACH,IAAW,QAAQ,KAAgB,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAS3D;;;;;OAKG;IACI,EAAE,CAAC,KAAa,EAAE,QAAuB;QAC9C,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,KAAa;QACtB,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACI,MAAM;QACX,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED;;;OAGG;IACI,OAAO;QACZ,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACI,YAAY,CAAC,SAAiB,EAAE,KAAY;QACjD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;YACzE,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;CACF","sourcesContent":["/**\n * dd-base-impl.ts 12.3.3\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\n/**\n * Type for event callback functions used in drag & drop operations.\n * Can return boolean to indicate if the event should continue propagation.\n */\nexport type EventCallback = (event: Event) => boolean|void;\n\n/**\n * Abstract base class for all drag & drop implementations.\n * Provides common functionality for event handling, enable/disable state,\n * and lifecycle management used by draggable, droppable, and resizable implementations.\n */\nexport abstract class DDBaseImplement {\n /**\n * Returns the current disabled state.\n * Note: Use enable()/disable() methods to change state as other operations need to happen.\n */\n public get disabled(): boolean { return this._disabled; }\n\n /** @internal */\n protected _disabled: boolean; // initial state to differentiate from false\n /** @internal */\n protected _eventRegister: {\n [eventName: string]: EventCallback;\n } = {};\n\n /**\n * Register an event callback for the specified event.\n *\n * @param event - Event name to listen for\n * @param callback - Function to call when event occurs\n */\n public on(event: string, callback: EventCallback): void {\n this._eventRegister[event] = callback;\n }\n\n /**\n * Unregister an event callback for the specified event.\n *\n * @param event - Event name to stop listening for\n */\n public off(event: string): void {\n delete this._eventRegister[event];\n }\n\n /**\n * Enable this drag & drop implementation.\n * Subclasses should override to perform additional setup.\n */\n public enable(): void {\n this._disabled = false;\n }\n\n /**\n * Disable this drag & drop implementation.\n * Subclasses should override to perform additional cleanup.\n */\n public disable(): void {\n this._disabled = true;\n }\n\n /**\n * Destroy this drag & drop implementation and clean up resources.\n * Removes all event handlers and clears internal state.\n */\n public destroy(): void {\n delete this._eventRegister;\n }\n\n /**\n * Trigger a registered event callback if one exists and the implementation is enabled.\n *\n * @param eventName - Name of the event to trigger\n * @param event - DOM event object to pass to the callback\n * @returns Result from the callback function, if any\n */\n public triggerEvent(eventName: string, event: Event): boolean|void {\n if (!this.disabled && this._eventRegister && this._eventRegister[eventName])\n return this._eventRegister[eventName](event);\n }\n}\n\n/**\n * Interface for HTML elements extended with drag & drop options.\n * Used to associate DD configuration with DOM elements.\n */\nexport interface HTMLElementExtendOpt<T> {\n /** The HTML element being extended */\n el: HTMLElement;\n /** The drag & drop options/configuration */\n option: T;\n /** Method to update the options and return the DD implementation */\n updateOption(T): DDBaseImplement;\n}\n"]}

View File

@@ -1,20 +0,0 @@
/**
* dd-draggable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';
import { GridItemHTMLElement, DDDragOpt } from './types';
type DDDragEvent = 'drag' | 'dragstart' | 'dragstop';
export declare class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt<DDDragOpt> {
el: GridItemHTMLElement;
option: DDDragOpt;
helper: HTMLElement;
constructor(el: GridItemHTMLElement, option?: DDDragOpt);
on(event: DDDragEvent, callback: (event: DragEvent) => void): void;
off(event: DDDragEvent): void;
enable(): void;
disable(forDestroy?: boolean): void;
destroy(): void;
updateOption(opts: DDDragOpt): DDDraggable;
}
export {};

View File

@@ -1,364 +0,0 @@
/**
* dd-draggable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDManager } from './dd-manager';
import { Utils } from './utils';
import { DDBaseImplement } from './dd-base-impl';
import { isTouch, touchend, touchmove, touchstart, pointerdown } from './dd-touch';
// make sure we are not clicking on known object that handles mouseDown
const skipMouseDown = 'input,textarea,button,select,option,[contenteditable="true"],.ui-resizable-handle';
// let count = 0; // TEST
class DDDraggable extends DDBaseImplement {
constructor(el, option = {}) {
super();
this.el = el;
this.option = option;
/** @internal */
this.dragTransform = {
xScale: 1,
yScale: 1,
xOffset: 0,
yOffset: 0
};
// get the element that is actually supposed to be dragged by
const handleName = option?.handle?.substring(1);
const n = el.gridstackNode;
this.dragEls = !handleName || el.classList.contains(handleName) ? [el] : (n?.subGrid ? [el.querySelector(option.handle) || el] : Array.from(el.querySelectorAll(option.handle)));
if (this.dragEls.length === 0) {
this.dragEls = [el];
}
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseDown = this._mouseDown.bind(this);
this._mouseMove = this._mouseMove.bind(this);
this._mouseUp = this._mouseUp.bind(this);
this._keyEvent = this._keyEvent.bind(this);
this.enable();
}
on(event, callback) {
super.on(event, callback);
}
off(event) {
super.off(event);
}
enable() {
if (this.disabled === false)
return;
super.enable();
this.dragEls.forEach(dragEl => {
dragEl.addEventListener('mousedown', this._mouseDown);
if (isTouch) {
dragEl.addEventListener('touchstart', touchstart);
dragEl.addEventListener('pointerdown', pointerdown);
// dragEl.style.touchAction = 'none'; // not needed unlike pointerdown doc comment
}
});
this.el.classList.remove('ui-draggable-disabled');
}
disable(forDestroy = false) {
if (this.disabled === true)
return;
super.disable();
this.dragEls.forEach(dragEl => {
dragEl.removeEventListener('mousedown', this._mouseDown);
if (isTouch) {
dragEl.removeEventListener('touchstart', touchstart);
dragEl.removeEventListener('pointerdown', pointerdown);
}
});
if (!forDestroy)
this.el.classList.add('ui-draggable-disabled');
}
destroy() {
if (this.dragTimeout)
window.clearTimeout(this.dragTimeout);
delete this.dragTimeout;
if (this.mouseDownEvent)
this._mouseUp(this.mouseDownEvent);
this.disable(true);
delete this.el;
delete this.helper;
delete this.option;
super.destroy();
}
updateOption(opts) {
Object.keys(opts).forEach(key => this.option[key] = opts[key]);
return this;
}
/** @internal call when mouse goes down before a dragstart happens */
_mouseDown(e) {
// don't let more than one widget handle mouseStart
if (DDManager.mouseHandled)
return;
if (e.button !== 0)
return true; // only left click
// make sure we are not clicking on known object that handles mouseDown, or ones supplied by the user
if (!this.dragEls.find(el => el === e.target) && e.target.closest(skipMouseDown))
return true;
if (this.option.cancel) {
if (e.target.closest(this.option.cancel))
return true;
}
this.mouseDownEvent = e;
delete this.dragging;
delete DDManager.dragElement;
delete DDManager.dropElement;
// document handler so we can continue receiving moves as the item is 'fixed' position, and capture=true so WE get a first crack
document.addEventListener('mousemove', this._mouseMove, { capture: true, passive: true }); // true=capture, not bubble
document.addEventListener('mouseup', this._mouseUp, true);
if (isTouch) {
e.currentTarget.addEventListener('touchmove', touchmove);
e.currentTarget.addEventListener('touchend', touchend);
}
e.preventDefault();
// preventDefault() prevents blur event which occurs just after mousedown event.
// if an editable content has focus, then blur must be call
if (document.activeElement)
document.activeElement.blur();
DDManager.mouseHandled = true;
return true;
}
/** @internal method to call actual drag event */
_callDrag(e) {
if (!this.dragging)
return;
const ev = Utils.initEvent(e, { target: this.el, type: 'drag' });
if (this.option.drag) {
this.option.drag(ev, this.ui());
}
this.triggerEvent('drag', ev);
}
/** @internal called when the main page (after successful mousedown) receives a move event to drag the item around the screen */
_mouseMove(e) {
// console.log(`${count++} move ${e.x},${e.y}`)
const s = this.mouseDownEvent;
this.lastDrag = e;
if (this.dragging) {
this._dragFollow(e);
// delay actual grid handling drag until we pause for a while if set
if (DDManager.pauseDrag) {
const pause = Number.isInteger(DDManager.pauseDrag) ? DDManager.pauseDrag : 100;
if (this.dragTimeout)
window.clearTimeout(this.dragTimeout);
this.dragTimeout = window.setTimeout(() => this._callDrag(e), pause);
}
else {
this._callDrag(e);
}
}
else if (Math.abs(e.x - s.x) + Math.abs(e.y - s.y) > 3) {
/**
* don't start unless we've moved at least 3 pixels
*/
this.dragging = true;
DDManager.dragElement = this;
// if we're dragging an actual grid item, set the current drop as the grid (to detect enter/leave)
const grid = this.el.gridstackNode?.grid;
if (grid) {
DDManager.dropElement = grid.el.ddElement.ddDroppable;
}
else {
delete DDManager.dropElement;
}
this.helper = this._createHelper();
this._setupHelperContainmentStyle();
this.dragTransform = Utils.getValuesFromTransformedElement(this.helperContainment);
this.dragOffset = this._getDragOffset(e, this.el, this.helperContainment);
this._setupHelperStyle(e);
const ev = Utils.initEvent(e, { target: this.el, type: 'dragstart' });
if (this.option.start) {
this.option.start(ev, this.ui());
}
this.triggerEvent('dragstart', ev);
// now track keyboard events to cancel or rotate
document.addEventListener('keydown', this._keyEvent);
}
// e.preventDefault(); // passive = true. OLD: was needed otherwise we get text sweep text selection as we drag around
return true;
}
/** @internal call when the mouse gets released to drop the item at current location */
_mouseUp(e) {
document.removeEventListener('mousemove', this._mouseMove, true);
document.removeEventListener('mouseup', this._mouseUp, true);
if (isTouch && e.currentTarget) { // destroy() during nested grid call us again wit fake _mouseUp
e.currentTarget.removeEventListener('touchmove', touchmove, true);
e.currentTarget.removeEventListener('touchend', touchend, true);
}
if (this.dragging) {
delete this.dragging;
delete this.el.gridstackNode?._origRotate;
document.removeEventListener('keydown', this._keyEvent);
// reset the drop target if dragging over ourself (already parented, just moving during stop callback below)
if (DDManager.dropElement?.el === this.el.parentElement) {
delete DDManager.dropElement;
}
this.helperContainment.style.position = this.parentOriginStylePosition || null;
if (this.helper !== this.el)
this.helper.remove(); // hide now
this._removeHelperStyle();
const ev = Utils.initEvent(e, { target: this.el, type: 'dragstop' });
if (this.option.stop) {
this.option.stop(ev); // NOTE: destroy() will be called when removing item, so expect NULL ptr after!
}
this.triggerEvent('dragstop', ev);
// call the droppable method to receive the item
if (DDManager.dropElement) {
DDManager.dropElement.drop(e);
}
}
delete this.helper;
delete this.mouseDownEvent;
delete DDManager.dragElement;
delete DDManager.dropElement;
delete DDManager.mouseHandled;
e.preventDefault();
}
/** @internal call when keys are being pressed - use Esc to cancel, R to rotate */
_keyEvent(e) {
const n = this.el.gridstackNode;
const grid = n?.grid || DDManager.dropElement?.el?.gridstack;
if (e.key === 'Escape') {
if (n && n._origRotate) {
n._orig = n._origRotate;
delete n._origRotate;
}
grid?.cancelDrag();
this._mouseUp(this.mouseDownEvent);
}
else if (n && grid && (e.key === 'r' || e.key === 'R')) {
if (!Utils.canBeRotated(n))
return;
n._origRotate = n._origRotate || { ...n._orig }; // store the real orig size in case we Esc after doing rotation
delete n._moving; // force rotate to happen (move waits for >50% coverage otherwise)
grid.setAnimation(false) // immediate rotate so _getDragOffset() gets the right dom size below
.rotate(n.el, { top: -this.dragOffset.offsetTop, left: -this.dragOffset.offsetLeft })
.setAnimation();
n._moving = true;
this.dragOffset = this._getDragOffset(this.lastDrag, n.el, this.helperContainment);
this.helper.style.width = this.dragOffset.width + 'px';
this.helper.style.height = this.dragOffset.height + 'px';
Utils.swap(n._orig, 'w', 'h');
delete n._rect;
this._mouseMove(this.lastDrag);
}
}
/** @internal create a clone copy (or user defined method) of the original drag item if set */
_createHelper() {
let helper = this.el;
if (typeof this.option.helper === 'function') {
helper = this.option.helper(this.el);
}
else if (this.option.helper === 'clone') {
helper = Utils.cloneNode(this.el);
}
if (!helper.parentElement) {
Utils.appendTo(helper, this.option.appendTo === 'parent' ? this.el.parentElement : this.option.appendTo);
}
this.dragElementOriginStyle = DDDraggable.originStyleProp.map(prop => this.el.style[prop]);
return helper;
}
/** @internal set the fix position of the dragged item */
_setupHelperStyle(e) {
this.helper.classList.add('ui-draggable-dragging');
this.el.gridstackNode?.grid?.el.classList.add('grid-stack-dragging');
// TODO: set all at once with style.cssText += ... ? https://stackoverflow.com/questions/3968593
const style = this.helper.style;
style.pointerEvents = 'none'; // needed for over items to get enter/leave
// style.cursor = 'move'; // TODO: can't set with pointerEvents=none ! (no longer in CSS either as no-op)
style.width = this.dragOffset.width + 'px';
style.height = this.dragOffset.height + 'px';
style.willChange = 'left, top';
style.position = 'fixed'; // let us drag between grids by not clipping as parent .grid-stack is position: 'relative'
this._dragFollow(e); // now position it
style.transition = 'none'; // show up instantly
setTimeout(() => {
if (this.helper) {
style.transition = null; // recover animation
}
}, 0);
return this;
}
/** @internal restore back the original style before dragging */
_removeHelperStyle() {
this.helper.classList.remove('ui-draggable-dragging');
this.el.gridstackNode?.grid?.el.classList.remove('grid-stack-dragging');
const node = this.helper?.gridstackNode;
// don't bother restoring styles if we're gonna remove anyway...
if (!node?._isAboutToRemove && this.dragElementOriginStyle) {
const helper = this.helper;
// don't animate, otherwise we animate offseted when switching back to 'absolute' from 'fixed'.
// TODO: this also removes resizing animation which doesn't have this issue, but others.
// Ideally both would animate ('move' would immediately restore 'absolute' and adjust coordinate to match,
// then trigger a delay (repaint) to restore to final dest with animate) but then we need to make sure 'resizestop'
// is called AFTER 'transitionend' event is received (see https://github.com/gridstack/gridstack.js/issues/2033)
const transition = this.dragElementOriginStyle['transition'] || null;
helper.style.transition = this.dragElementOriginStyle['transition'] = 'none'; // can't be NULL #1973
DDDraggable.originStyleProp.forEach(prop => helper.style[prop] = this.dragElementOriginStyle[prop] || null);
setTimeout(() => helper.style.transition = transition, 50); // recover animation from saved vars after a pause (0 isn't enough #1973)
}
delete this.dragElementOriginStyle;
return this;
}
/** @internal updates the top/left position to follow the mouse */
_dragFollow(e) {
const containmentRect = { left: 0, top: 0 };
// if (this.helper.style.position === 'absolute') { // we use 'fixed'
// const { left, top } = this.helperContainment.getBoundingClientRect();
// containmentRect = { left, top };
// }
const style = this.helper.style;
const offset = this.dragOffset;
style.left = (e.clientX + offset.offsetLeft - containmentRect.left) * this.dragTransform.xScale + 'px';
style.top = (e.clientY + offset.offsetTop - containmentRect.top) * this.dragTransform.yScale + 'px';
}
/** @internal */
_setupHelperContainmentStyle() {
this.helperContainment = this.helper.parentElement;
if (this.helper.style.position !== 'fixed') {
this.parentOriginStylePosition = this.helperContainment.style.position;
if (getComputedStyle(this.helperContainment).position.match(/static/)) {
this.helperContainment.style.position = 'relative';
}
}
return this;
}
/** @internal */
_getDragOffset(event, el, parent) {
// in case ancestor has transform/perspective css properties that change the viewpoint
let xformOffsetX = 0;
let xformOffsetY = 0;
if (parent) {
xformOffsetX = this.dragTransform.xOffset;
xformOffsetY = this.dragTransform.yOffset;
}
const targetOffset = el.getBoundingClientRect();
return {
left: targetOffset.left,
top: targetOffset.top,
offsetLeft: -event.clientX + targetOffset.left - xformOffsetX,
offsetTop: -event.clientY + targetOffset.top - xformOffsetY,
width: targetOffset.width * this.dragTransform.xScale,
height: targetOffset.height * this.dragTransform.yScale
};
}
/** @internal TODO: set to public as called by DDDroppable! */
ui() {
const containmentEl = this.el.parentElement;
const containmentRect = containmentEl.getBoundingClientRect();
const offset = this.helper.getBoundingClientRect();
return {
position: {
top: (offset.top - containmentRect.top) * this.dragTransform.yScale,
left: (offset.left - containmentRect.left) * this.dragTransform.xScale
}
/* not used by GridStack for now...
helper: [this.helper], //The object arr representing the helper that's being dragged.
offset: { top: offset.top, left: offset.left } // Current offset position of the helper as { top, left } object.
*/
};
}
}
/** @internal properties we change during dragging, and restore back */
DDDraggable.originStyleProp = ['width', 'height', 'transform', 'transform-origin', 'transition', 'pointerEvents', 'position', 'left', 'top', 'minWidth', 'willChange'];
export { DDDraggable };
//# sourceMappingURL=dd-draggable.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +0,0 @@
/**
* dd-droppable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';
import { DDUIData } from './types';
export interface DDDroppableOpt {
accept?: string | ((el: HTMLElement) => boolean);
drop?: (event: DragEvent, ui: DDUIData) => void;
over?: (event: DragEvent, ui: DDUIData) => void;
out?: (event: DragEvent, ui: DDUIData) => void;
}
export declare class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt<DDDroppableOpt> {
el: HTMLElement;
option: DDDroppableOpt;
accept: (el: HTMLElement) => boolean;
constructor(el: HTMLElement, option?: DDDroppableOpt);
on(event: 'drop' | 'dropover' | 'dropout', callback: (event: DragEvent) => void): void;
off(event: 'drop' | 'dropover' | 'dropout'): void;
enable(): void;
disable(forDestroy?: boolean): void;
destroy(): void;
updateOption(opts: DDDroppableOpt): DDDroppable;
/** item is being dropped on us - called by the drag mouseup handler - this calls the client drop event */
drop(e: MouseEvent): void;
}

View File

@@ -1,149 +0,0 @@
/**
* dd-droppable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDManager } from './dd-manager';
import { DDBaseImplement } from './dd-base-impl';
import { Utils } from './utils';
import { isTouch, pointerenter, pointerleave } from './dd-touch';
// let count = 0; // TEST
export class DDDroppable extends DDBaseImplement {
constructor(el, option = {}) {
super();
this.el = el;
this.option = option;
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseEnter = this._mouseEnter.bind(this);
this._mouseLeave = this._mouseLeave.bind(this);
this.enable();
this._setupAccept();
}
on(event, callback) {
super.on(event, callback);
}
off(event) {
super.off(event);
}
enable() {
if (this.disabled === false)
return;
super.enable();
this.el.classList.add('ui-droppable');
this.el.classList.remove('ui-droppable-disabled');
this.el.addEventListener('mouseenter', this._mouseEnter);
this.el.addEventListener('mouseleave', this._mouseLeave);
if (isTouch) {
this.el.addEventListener('pointerenter', pointerenter);
this.el.addEventListener('pointerleave', pointerleave);
}
}
disable(forDestroy = false) {
if (this.disabled === true)
return;
super.disable();
this.el.classList.remove('ui-droppable');
if (!forDestroy)
this.el.classList.add('ui-droppable-disabled');
this.el.removeEventListener('mouseenter', this._mouseEnter);
this.el.removeEventListener('mouseleave', this._mouseLeave);
if (isTouch) {
this.el.removeEventListener('pointerenter', pointerenter);
this.el.removeEventListener('pointerleave', pointerleave);
}
}
destroy() {
this.disable(true);
this.el.classList.remove('ui-droppable');
this.el.classList.remove('ui-droppable-disabled');
super.destroy();
}
updateOption(opts) {
Object.keys(opts).forEach(key => this.option[key] = opts[key]);
this._setupAccept();
return this;
}
/** @internal called when the cursor enters our area - prepare for a possible drop and track leaving */
_mouseEnter(e) {
// console.log(`${count++} Enter ${this.el.id || (this.el as GridHTMLElement).gridstack.opts.id}`); // TEST
if (!DDManager.dragElement)
return;
if (!this._canDrop(DDManager.dragElement.el))
return;
e.preventDefault();
e.stopPropagation();
// make sure when we enter this, that the last one gets a leave FIRST to correctly cleanup as we don't always do
if (DDManager.dropElement && DDManager.dropElement !== this) {
DDManager.dropElement._mouseLeave(e, true); // calledByEnter = true
}
DDManager.dropElement = this;
const ev = Utils.initEvent(e, { target: this.el, type: 'dropover' });
if (this.option.over) {
this.option.over(ev, this._ui(DDManager.dragElement));
}
this.triggerEvent('dropover', ev);
this.el.classList.add('ui-droppable-over');
// console.log('tracking'); // TEST
}
/** @internal called when the item is leaving our area, stop tracking if we had moving item */
_mouseLeave(e, calledByEnter = false) {
// console.log(`${count++} Leave ${this.el.id || (this.el as GridHTMLElement).gridstack.opts.id}`); // TEST
if (!DDManager.dragElement || DDManager.dropElement !== this)
return;
e.preventDefault();
e.stopPropagation();
const ev = Utils.initEvent(e, { target: this.el, type: 'dropout' });
if (this.option.out) {
this.option.out(ev, this._ui(DDManager.dragElement));
}
this.triggerEvent('dropout', ev);
if (DDManager.dropElement === this) {
delete DDManager.dropElement;
// console.log('not tracking'); // TEST
// if we're still over a parent droppable, send it an enter as we don't get one from leaving nested children
if (!calledByEnter) {
let parentDrop;
let parent = this.el.parentElement;
while (!parentDrop && parent) {
parentDrop = parent.ddElement?.ddDroppable;
parent = parent.parentElement;
}
if (parentDrop) {
parentDrop._mouseEnter(e);
}
}
}
}
/** item is being dropped on us - called by the drag mouseup handler - this calls the client drop event */
drop(e) {
e.preventDefault();
const ev = Utils.initEvent(e, { target: this.el, type: 'drop' });
if (this.option.drop) {
this.option.drop(ev, this._ui(DDManager.dragElement));
}
this.triggerEvent('drop', ev);
}
/** @internal true if element matches the string/method accept option */
_canDrop(el) {
return el && (!this.accept || this.accept(el));
}
/** @internal */
_setupAccept() {
if (!this.option.accept)
return this;
if (typeof this.option.accept === 'string') {
this.accept = (el) => el.classList.contains(this.option.accept) || el.matches(this.option.accept);
}
else {
this.accept = this.option.accept;
}
return this;
}
/** @internal */
_ui(drag) {
return {
draggable: drag.el,
...drag.ui()
};
}
}
//# sourceMappingURL=dd-droppable.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,27 +0,0 @@
/**
* dd-elements.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizable, DDResizableOpt } from './dd-resizable';
import { DDDragOpt, GridItemHTMLElement } from './types';
import { DDDraggable } from './dd-draggable';
import { DDDroppable, DDDroppableOpt } from './dd-droppable';
export interface DDElementHost extends GridItemHTMLElement {
ddElement?: DDElement;
}
export declare class DDElement {
el: DDElementHost;
static init(el: DDElementHost): DDElement;
ddDraggable?: DDDraggable;
ddDroppable?: DDDroppable;
ddResizable?: DDResizable;
constructor(el: DDElementHost);
on(eventName: string, callback: (event: MouseEvent) => void): DDElement;
off(eventName: string): DDElement;
setupDraggable(opts: DDDragOpt): DDElement;
cleanDraggable(): DDElement;
setupResizable(opts: DDResizableOpt): DDElement;
cleanResizable(): DDElement;
setupDroppable(opts: DDDroppableOpt): DDElement;
cleanDroppable(): DDElement;
}

View File

@@ -1,91 +0,0 @@
/**
* dd-elements.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizable } from './dd-resizable';
import { DDDraggable } from './dd-draggable';
import { DDDroppable } from './dd-droppable';
export class DDElement {
static init(el) {
if (!el.ddElement) {
el.ddElement = new DDElement(el);
}
return el.ddElement;
}
constructor(el) {
this.el = el;
}
on(eventName, callback) {
if (this.ddDraggable && ['drag', 'dragstart', 'dragstop'].indexOf(eventName) > -1) {
this.ddDraggable.on(eventName, callback);
}
else if (this.ddDroppable && ['drop', 'dropover', 'dropout'].indexOf(eventName) > -1) {
this.ddDroppable.on(eventName, callback);
}
else if (this.ddResizable && ['resizestart', 'resize', 'resizestop'].indexOf(eventName) > -1) {
this.ddResizable.on(eventName, callback);
}
return this;
}
off(eventName) {
if (this.ddDraggable && ['drag', 'dragstart', 'dragstop'].indexOf(eventName) > -1) {
this.ddDraggable.off(eventName);
}
else if (this.ddDroppable && ['drop', 'dropover', 'dropout'].indexOf(eventName) > -1) {
this.ddDroppable.off(eventName);
}
else if (this.ddResizable && ['resizestart', 'resize', 'resizestop'].indexOf(eventName) > -1) {
this.ddResizable.off(eventName);
}
return this;
}
setupDraggable(opts) {
if (!this.ddDraggable) {
this.ddDraggable = new DDDraggable(this.el, opts);
}
else {
this.ddDraggable.updateOption(opts);
}
return this;
}
cleanDraggable() {
if (this.ddDraggable) {
this.ddDraggable.destroy();
delete this.ddDraggable;
}
return this;
}
setupResizable(opts) {
if (!this.ddResizable) {
this.ddResizable = new DDResizable(this.el, opts);
}
else {
this.ddResizable.updateOption(opts);
}
return this;
}
cleanResizable() {
if (this.ddResizable) {
this.ddResizable.destroy();
delete this.ddResizable;
}
return this;
}
setupDroppable(opts) {
if (!this.ddDroppable) {
this.ddDroppable = new DDDroppable(this.el, opts);
}
else {
this.ddDroppable.updateOption(opts);
}
return this;
}
cleanDroppable() {
if (this.ddDroppable) {
this.ddDroppable.destroy();
delete this.ddDroppable;
}
return this;
}
}
//# sourceMappingURL=dd-element.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,82 +0,0 @@
/**
* dd-gridstack.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { GridItemHTMLElement, GridStackElement, DDDragOpt } from './types';
import { DDElementHost } from './dd-element';
/**
* Drag & Drop options for drop targets.
* Configures which elements can be dropped onto a grid.
*/
export type DDDropOpt = {
/** Function to determine if an element can be dropped (see GridStackOptions.acceptWidgets) */
accept?: (el: GridItemHTMLElement) => boolean;
};
/**
* Drag & Drop operation types used throughout the DD system.
* Can be control commands or configuration objects.
*/
export type DDOpts = 'enable' | 'disable' | 'destroy' | 'option' | string | any;
/**
* Keys for DD configuration options that can be set via the 'option' command.
*/
export type DDKey = 'minWidth' | 'minHeight' | 'maxWidth' | 'maxHeight' | 'maxHeightMoveUp' | 'maxWidthMoveLeft';
/**
* Values for DD configuration options (numbers or strings with units).
*/
export type DDValue = number | string;
/**
* Callback function type for drag & drop events.
*
* @param event - The DOM event that triggered the callback
* @param arg2 - The grid item element being dragged/dropped
* @param helper - Optional helper element used during drag operations
*/
export type DDCallback = (event: Event, arg2: GridItemHTMLElement, helper?: GridItemHTMLElement) => void;
/**
* HTML Native Mouse and Touch Events Drag and Drop functionality.
*
* This class provides the main drag & drop implementation for GridStack,
* handling resizing, dragging, and dropping of grid items using native HTML5 events.
* It manages the interaction between different DD components and the grid system.
*/
export declare class DDGridStack {
/**
* Enable/disable/configure resizing for grid elements.
*
* @param el - Grid item element(s) to configure
* @param opts - Resize options or command ('enable', 'disable', 'destroy', 'option', or config object)
* @param key - Option key when using 'option' command
* @param value - Option value when using 'option' command
* @returns this instance for chaining
*
* @example
* dd.resizable(element, 'enable'); // Enable resizing
* dd.resizable(element, 'option', 'minWidth', 100); // Set minimum width
*/
resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): DDGridStack;
/**
* Enable/disable/configure dragging for grid elements.
*
* @param el - Grid item element(s) to configure
* @param opts - Drag options or command ('enable', 'disable', 'destroy', 'option', or config object)
* @param key - Option key when using 'option' command
* @param value - Option value when using 'option' command
* @returns this instance for chaining
*
* @example
* dd.draggable(element, 'enable'); // Enable dragging
* dd.draggable(element, {handle: '.drag-handle'}); // Configure drag handle
*/
draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): DDGridStack;
dragIn(el: GridStackElement, opts: DDDragOpt): DDGridStack;
droppable(el: GridItemHTMLElement, opts: DDOpts | DDDropOpt, key?: DDKey, value?: DDValue): DDGridStack;
/** true if element is droppable */
isDroppable(el: DDElementHost): boolean;
/** true if element is draggable */
isDraggable(el: DDElementHost): boolean;
/** true if element is draggable */
isResizable(el: DDElementHost): boolean;
on(el: GridItemHTMLElement, name: string, callback: DDCallback): DDGridStack;
off(el: GridItemHTMLElement, name: string): DDGridStack;
}

View File

@@ -1,165 +0,0 @@
/**
* dd-gridstack.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { Utils } from './utils';
import { DDManager } from './dd-manager';
import { DDElement } from './dd-element';
// let count = 0; // TEST
/**
* HTML Native Mouse and Touch Events Drag and Drop functionality.
*
* This class provides the main drag & drop implementation for GridStack,
* handling resizing, dragging, and dropping of grid items using native HTML5 events.
* It manages the interaction between different DD components and the grid system.
*/
export class DDGridStack {
/**
* Enable/disable/configure resizing for grid elements.
*
* @param el - Grid item element(s) to configure
* @param opts - Resize options or command ('enable', 'disable', 'destroy', 'option', or config object)
* @param key - Option key when using 'option' command
* @param value - Option value when using 'option' command
* @returns this instance for chaining
*
* @example
* dd.resizable(element, 'enable'); // Enable resizing
* dd.resizable(element, 'option', 'minWidth', 100); // Set minimum width
*/
resizable(el, opts, key, value) {
this._getDDElements(el, opts).forEach(dEl => {
if (opts === 'disable' || opts === 'enable') {
dEl.ddResizable && dEl.ddResizable[opts](); // can't create DD as it requires options for setupResizable()
}
else if (opts === 'destroy') {
dEl.ddResizable && dEl.cleanResizable();
}
else if (opts === 'option') {
dEl.setupResizable({ [key]: value });
}
else {
const n = dEl.el.gridstackNode;
const grid = n.grid;
let handles = dEl.el.getAttribute('gs-resize-handles') || grid.opts.resizable.handles || 'e,s,se';
if (handles === 'all')
handles = 'n,e,s,w,se,sw,ne,nw';
// NOTE: keep the resize handles as e,w don't have enough space (10px) to show resize corners anyway. limit during drag instead
// restrict vertical resize if height is done to match content anyway... odd to have it spring back
// if (Utils.shouldSizeToContent(n, true)) {
// const doE = handles.indexOf('e') !== -1;
// const doW = handles.indexOf('w') !== -1;
// handles = doE ? (doW ? 'e,w' : 'e') : (doW ? 'w' : '');
// }
const autoHide = !grid.opts.alwaysShowResizeHandle;
dEl.setupResizable({
...grid.opts.resizable,
...{ handles, autoHide },
...{
start: opts.start,
stop: opts.stop,
resize: opts.resize
}
});
}
});
return this;
}
/**
* Enable/disable/configure dragging for grid elements.
*
* @param el - Grid item element(s) to configure
* @param opts - Drag options or command ('enable', 'disable', 'destroy', 'option', or config object)
* @param key - Option key when using 'option' command
* @param value - Option value when using 'option' command
* @returns this instance for chaining
*
* @example
* dd.draggable(element, 'enable'); // Enable dragging
* dd.draggable(element, {handle: '.drag-handle'}); // Configure drag handle
*/
draggable(el, opts, key, value) {
this._getDDElements(el, opts).forEach(dEl => {
if (opts === 'disable' || opts === 'enable') {
dEl.ddDraggable && dEl.ddDraggable[opts](); // can't create DD as it requires options for setupDraggable()
}
else if (opts === 'destroy') {
dEl.ddDraggable && dEl.cleanDraggable();
}
else if (opts === 'option') {
dEl.setupDraggable({ [key]: value });
}
else {
const grid = dEl.el.gridstackNode.grid;
dEl.setupDraggable({
...grid.opts.draggable,
...{
// containment: (grid.parentGridNode && grid.opts.dragOut === false) ? grid.el.parentElement : (grid.opts.draggable.containment || null),
start: opts.start,
stop: opts.stop,
drag: opts.drag
}
});
}
});
return this;
}
dragIn(el, opts) {
this._getDDElements(el).forEach(dEl => dEl.setupDraggable(opts));
return this;
}
droppable(el, opts, key, value) {
if (typeof opts.accept === 'function' && !opts._accept) {
opts._accept = opts.accept;
opts.accept = (el) => opts._accept(el);
}
this._getDDElements(el, opts).forEach(dEl => {
if (opts === 'disable' || opts === 'enable') {
dEl.ddDroppable && dEl.ddDroppable[opts]();
}
else if (opts === 'destroy') {
dEl.ddDroppable && dEl.cleanDroppable();
}
else if (opts === 'option') {
dEl.setupDroppable({ [key]: value });
}
else {
dEl.setupDroppable(opts);
}
});
return this;
}
/** true if element is droppable */
isDroppable(el) {
return !!(el?.ddElement?.ddDroppable && !el.ddElement.ddDroppable.disabled);
}
/** true if element is draggable */
isDraggable(el) {
return !!(el?.ddElement?.ddDraggable && !el.ddElement.ddDraggable.disabled);
}
/** true if element is draggable */
isResizable(el) {
return !!(el?.ddElement?.ddResizable && !el.ddElement.ddResizable.disabled);
}
on(el, name, callback) {
this._getDDElements(el).forEach(dEl => dEl.on(name, (event) => {
callback(event, DDManager.dragElement ? DDManager.dragElement.el : event.target, DDManager.dragElement ? DDManager.dragElement.helper : null);
}));
return this;
}
off(el, name) {
this._getDDElements(el).forEach(dEl => dEl.off(name));
return this;
}
/** @internal returns a list of DD elements, creating them on the fly by default unless option is to destroy or disable */
_getDDElements(els, opts) {
// don't force create if we're going to destroy it, unless it's a grid which is used as drop target for it's children
const create = els.gridstack || opts !== 'destroy' && opts !== 'disable';
const hosts = Utils.getElements(els);
if (!hosts.length)
return [];
const list = hosts.map(e => e.ddElement || (create ? DDElement.init(e) : null)).filter(d => d); // remove nulls
return list;
}
}
//# sourceMappingURL=dd-gridstack.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,43 +0,0 @@
/**
* dd-manager.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDDraggable } from './dd-draggable';
import { DDDroppable } from './dd-droppable';
import { DDResizable } from './dd-resizable';
/**
* Global state manager for all Drag & Drop instances.
*
* This class maintains shared state across all drag & drop operations,
* ensuring proper coordination between multiple grids and drag/drop elements.
* All properties are static to provide global access throughout the DD system.
*/
export declare class DDManager {
/**
* Controls drag operation pausing behavior.
* If set to true or a number (milliseconds), dragging placement and collision
* detection will only happen after the user pauses movement.
* This improves performance during rapid mouse movements.
*/
static pauseDrag: boolean | number;
/**
* Flag indicating if a mouse down event was already handled.
* Prevents multiple handlers from processing the same mouse event.
*/
static mouseHandled: boolean;
/**
* Reference to the element currently being dragged.
* Used to track the active drag operation across the system.
*/
static dragElement: DDDraggable;
/**
* Reference to the drop target element currently under the cursor.
* Used to handle drop operations and hover effects.
*/
static dropElement: DDDroppable;
/**
* Reference to the element currently being resized.
* Helps ignore nested grid resize handles during resize operations.
*/
static overResizeElement: DDResizable;
}

View File

@@ -1,14 +0,0 @@
/**
* dd-manager.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
/**
* Global state manager for all Drag & Drop instances.
*
* This class maintains shared state across all drag & drop operations,
* ensuring proper coordination between multiple grids and drag/drop elements.
* All properties are static to provide global access throughout the DD system.
*/
export class DDManager {
}
//# sourceMappingURL=dd-manager.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"dd-manager.js","sourceRoot":"","sources":["../../src/dd-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;CAiCrB","sourcesContent":["/**\n * dd-manager.ts 12.3.3\n * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license\n */\n\nimport { DDDraggable } from './dd-draggable';\nimport { DDDroppable } from './dd-droppable';\nimport { DDResizable } from './dd-resizable';\n\n/**\n * Global state manager for all Drag & Drop instances.\n *\n * This class maintains shared state across all drag & drop operations,\n * ensuring proper coordination between multiple grids and drag/drop elements.\n * All properties are static to provide global access throughout the DD system.\n */\nexport class DDManager {\n /**\n * Controls drag operation pausing behavior.\n * If set to true or a number (milliseconds), dragging placement and collision\n * detection will only happen after the user pauses movement.\n * This improves performance during rapid mouse movements.\n */\n public static pauseDrag: boolean | number;\n\n /**\n * Flag indicating if a mouse down event was already handled.\n * Prevents multiple handlers from processing the same mouse event.\n */\n public static mouseHandled: boolean;\n\n /**\n * Reference to the element currently being dragged.\n * Used to track the active drag operation across the system.\n */\n public static dragElement: DDDraggable;\n\n /**\n * Reference to the drop target element currently under the cursor.\n * Used to handle drop operations and hover effects.\n */\n public static dropElement: DDDroppable;\n\n /**\n * Reference to the element currently being resized.\n * Helps ignore nested grid resize handles during resize operations.\n */\n public static overResizeElement: DDResizable;\n\n}\n"]}

View File

@@ -1,18 +0,0 @@
/**
* dd-resizable-handle.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { GridItemHTMLElement } from './gridstack';
export interface DDResizableHandleOpt {
start?: (event: any) => void;
move?: (event: any) => void;
stop?: (event: any) => void;
}
export declare class DDResizableHandle {
protected host: GridItemHTMLElement;
protected dir: string;
protected option: DDResizableHandleOpt;
constructor(host: GridItemHTMLElement, dir: string, option: DDResizableHandleOpt);
/** call this when resize handle needs to be removed and cleaned up */
destroy(): DDResizableHandle;
}

View File

@@ -1,113 +0,0 @@
/**
* dd-resizable-handle.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { isTouch, pointerdown, touchend, touchmove, touchstart } from './dd-touch';
class DDResizableHandle {
constructor(host, dir, option) {
this.host = host;
this.dir = dir;
this.option = option;
/** @internal true after we've moved enough pixels to start a resize */
this.moving = false;
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseDown = this._mouseDown.bind(this);
this._mouseMove = this._mouseMove.bind(this);
this._mouseUp = this._mouseUp.bind(this);
this._keyEvent = this._keyEvent.bind(this);
this._init();
}
/** @internal */
_init() {
const el = this.el = document.createElement('div');
el.classList.add('ui-resizable-handle');
el.classList.add(`${DDResizableHandle.prefix}${this.dir}`);
el.style.zIndex = '100';
el.style.userSelect = 'none';
this.host.appendChild(this.el);
this.el.addEventListener('mousedown', this._mouseDown);
if (isTouch) {
this.el.addEventListener('touchstart', touchstart);
this.el.addEventListener('pointerdown', pointerdown);
// this.el.style.touchAction = 'none'; // not needed unlike pointerdown doc comment
}
return this;
}
/** call this when resize handle needs to be removed and cleaned up */
destroy() {
if (this.moving)
this._mouseUp(this.mouseDownEvent);
this.el.removeEventListener('mousedown', this._mouseDown);
if (isTouch) {
this.el.removeEventListener('touchstart', touchstart);
this.el.removeEventListener('pointerdown', pointerdown);
}
this.host.removeChild(this.el);
delete this.el;
delete this.host;
return this;
}
/** @internal called on mouse down on us: capture move on the entire document (mouse might not stay on us) until we release the mouse */
_mouseDown(e) {
this.mouseDownEvent = e;
document.addEventListener('mousemove', this._mouseMove, { capture: true, passive: true }); // capture, not bubble
document.addEventListener('mouseup', this._mouseUp, true);
if (isTouch) {
this.el.addEventListener('touchmove', touchmove);
this.el.addEventListener('touchend', touchend);
}
e.stopPropagation();
e.preventDefault();
}
/** @internal */
_mouseMove(e) {
const s = this.mouseDownEvent;
if (this.moving) {
this._triggerEvent('move', e);
}
else if (Math.abs(e.x - s.x) + Math.abs(e.y - s.y) > 2) {
// don't start unless we've moved at least 3 pixels
this.moving = true;
this._triggerEvent('start', this.mouseDownEvent);
this._triggerEvent('move', e);
// now track keyboard events to cancel
document.addEventListener('keydown', this._keyEvent);
}
e.stopPropagation();
// e.preventDefault(); passive = true
}
/** @internal */
_mouseUp(e) {
if (this.moving) {
this._triggerEvent('stop', e);
document.removeEventListener('keydown', this._keyEvent);
}
document.removeEventListener('mousemove', this._mouseMove, true);
document.removeEventListener('mouseup', this._mouseUp, true);
if (isTouch) {
this.el.removeEventListener('touchmove', touchmove);
this.el.removeEventListener('touchend', touchend);
}
delete this.moving;
delete this.mouseDownEvent;
e.stopPropagation();
e.preventDefault();
}
/** @internal call when keys are being pressed - use Esc to cancel */
_keyEvent(e) {
if (e.key === 'Escape') {
this.host.gridstackNode?.grid?.engine.restoreInitial();
this._mouseUp(this.mouseDownEvent);
}
}
/** @internal */
_triggerEvent(name, event) {
if (this.option[name])
this.option[name](event);
return this;
}
}
/** @internal */
DDResizableHandle.prefix = 'ui-resizable-';
export { DDResizableHandle };
//# sourceMappingURL=dd-resizable-handle.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,30 +0,0 @@
/**
* dd-resizable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';
import { DDUIData, GridItemHTMLElement } from './types';
export interface DDResizableOpt {
autoHide?: boolean;
handles?: string;
maxHeight?: number;
maxHeightMoveUp?: number;
maxWidth?: number;
maxWidthMoveLeft?: number;
minHeight?: number;
minWidth?: number;
start?: (event: Event, ui: DDUIData) => void;
stop?: (event: Event) => void;
resize?: (event: Event, ui: DDUIData) => void;
}
export declare class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt<DDResizableOpt> {
el: GridItemHTMLElement;
option: DDResizableOpt;
constructor(el: GridItemHTMLElement, option?: DDResizableOpt);
on(event: 'resizestart' | 'resize' | 'resizestop', callback: (event: DragEvent) => void): void;
off(event: 'resizestart' | 'resize' | 'resizestop'): void;
enable(): void;
disable(): void;
destroy(): void;
updateOption(opts: DDResizableOpt): DDResizable;
}

View File

@@ -1,304 +0,0 @@
/**
* dd-resizable.ts 12.3.3
* Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license
*/
import { DDResizableHandle } from './dd-resizable-handle';
import { DDBaseImplement } from './dd-base-impl';
import { Utils } from './utils';
import { DDManager } from './dd-manager';
class DDResizable extends DDBaseImplement {
// have to be public else complains for HTMLElementExtendOpt ?
constructor(el, option = {}) {
super();
this.el = el;
this.option = option;
/** @internal */
this.rectScale = { x: 1, y: 1 };
/** @internal */
this._ui = () => {
const containmentEl = this.el.parentElement;
const containmentRect = containmentEl.getBoundingClientRect();
const newRect = {
width: this.originalRect.width,
height: this.originalRect.height + this.scrolled,
left: this.originalRect.left,
top: this.originalRect.top - this.scrolled
};
const rect = this.temporalRect || newRect;
return {
position: {
left: (rect.left - containmentRect.left) * this.rectScale.x,
top: (rect.top - containmentRect.top) * this.rectScale.y
},
size: {
width: rect.width * this.rectScale.x,
height: rect.height * this.rectScale.y
}
/* Gridstack ONLY needs position set above... keep around in case.
element: [this.el], // The object representing the element to be resized
helper: [], // TODO: not support yet - The object representing the helper that's being resized
originalElement: [this.el],// we don't wrap here, so simplify as this.el //The object representing the original element before it is wrapped
originalPosition: { // The position represented as { left, top } before the resizable is resized
left: this.originalRect.left - containmentRect.left,
top: this.originalRect.top - containmentRect.top
},
originalSize: { // The size represented as { width, height } before the resizable is resized
width: this.originalRect.width,
height: this.originalRect.height
}
*/
};
};
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseOver = this._mouseOver.bind(this);
this._mouseOut = this._mouseOut.bind(this);
this.enable();
this._setupAutoHide(this.option.autoHide);
this._setupHandlers();
}
on(event, callback) {
super.on(event, callback);
}
off(event) {
super.off(event);
}
enable() {
super.enable();
this.el.classList.remove('ui-resizable-disabled');
this._setupAutoHide(this.option.autoHide);
}
disable() {
super.disable();
this.el.classList.add('ui-resizable-disabled');
this._setupAutoHide(false);
}
destroy() {
this._removeHandlers();
this._setupAutoHide(false);
delete this.el;
super.destroy();
}
updateOption(opts) {
const updateHandles = (opts.handles && opts.handles !== this.option.handles);
const updateAutoHide = (opts.autoHide && opts.autoHide !== this.option.autoHide);
Object.keys(opts).forEach(key => this.option[key] = opts[key]);
if (updateHandles) {
this._removeHandlers();
this._setupHandlers();
}
if (updateAutoHide) {
this._setupAutoHide(this.option.autoHide);
}
return this;
}
/** @internal turns auto hide on/off */
_setupAutoHide(auto) {
if (auto) {
this.el.classList.add('ui-resizable-autohide');
// use mouseover and not mouseenter to get better performance and track for nested cases
this.el.addEventListener('mouseover', this._mouseOver);
this.el.addEventListener('mouseout', this._mouseOut);
}
else {
this.el.classList.remove('ui-resizable-autohide');
this.el.removeEventListener('mouseover', this._mouseOver);
this.el.removeEventListener('mouseout', this._mouseOut);
if (DDManager.overResizeElement === this) {
delete DDManager.overResizeElement;
}
}
return this;
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_mouseOver(e) {
// console.log(`${count++} pre-enter ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
// already over a child, ignore. Ideally we just call e.stopPropagation() but see https://github.com/gridstack/gridstack.js/issues/2018
if (DDManager.overResizeElement || DDManager.dragElement)
return;
DDManager.overResizeElement = this;
// console.log(`${count++} enter ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
this.el.classList.remove('ui-resizable-autohide');
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_mouseOut(e) {
// console.log(`${count++} pre-leave ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
if (DDManager.overResizeElement !== this)
return;
delete DDManager.overResizeElement;
// console.log(`${count++} leave ${(this.el as GridItemHTMLElement).gridstackNode._id}`)
this.el.classList.add('ui-resizable-autohide');
}
/** @internal */
_setupHandlers() {
this.handlers = this.option.handles.split(',')
.map(dir => dir.trim())
.map(dir => new DDResizableHandle(this.el, dir, {
start: (event) => {
this._resizeStart(event);
},
stop: (event) => {
this._resizeStop(event);
},
move: (event) => {
this._resizing(event, dir);
}
}));
return this;
}
/** @internal */
_resizeStart(event) {
this.sizeToContent = Utils.shouldSizeToContent(this.el.gridstackNode, true); // strick true only and not number
this.originalRect = this.el.getBoundingClientRect();
this.scrollEl = Utils.getScrollElement(this.el);
this.scrollY = this.scrollEl.scrollTop;
this.scrolled = 0;
this.startEvent = event;
this._setupHelper();
this._applyChange();
const ev = Utils.initEvent(event, { type: 'resizestart', target: this.el });
if (this.option.start) {
this.option.start(ev, this._ui());
}
this.el.classList.add('ui-resizable-resizing');
this.triggerEvent('resizestart', ev);
return this;
}
/** @internal */
_resizing(event, dir) {
this.scrolled = this.scrollEl.scrollTop - this.scrollY;
this.temporalRect = this._getChange(event, dir);
this._applyChange();
const ev = Utils.initEvent(event, { type: 'resize', target: this.el });
if (this.option.resize) {
this.option.resize(ev, this._ui());
}
this.triggerEvent('resize', ev);
return this;
}
/** @internal */
_resizeStop(event) {
const ev = Utils.initEvent(event, { type: 'resizestop', target: this.el });
// Remove style attr now, so the stop handler can rebuild style attrs
this._cleanHelper();
if (this.option.stop) {
this.option.stop(ev); // Note: ui() not used by gridstack so don't pass
}
this.el.classList.remove('ui-resizable-resizing');
this.triggerEvent('resizestop', ev);
delete this.startEvent;
delete this.originalRect;
delete this.temporalRect;
delete this.scrollY;
delete this.scrolled;
return this;
}
/** @internal */
_setupHelper() {
this.elOriginStyleVal = DDResizable._originStyleProp.map(prop => this.el.style[prop]);
this.parentOriginStylePosition = this.el.parentElement.style.position;
const parent = this.el.parentElement;
const dragTransform = Utils.getValuesFromTransformedElement(parent);
this.rectScale = {
x: dragTransform.xScale,
y: dragTransform.yScale
};
if (getComputedStyle(this.el.parentElement).position.match(/static/)) {
this.el.parentElement.style.position = 'relative';
}
this.el.style.position = 'absolute';
this.el.style.opacity = '0.8';
return this;
}
/** @internal */
_cleanHelper() {
DDResizable._originStyleProp.forEach((prop, i) => {
this.el.style[prop] = this.elOriginStyleVal[i] || null;
});
this.el.parentElement.style.position = this.parentOriginStylePosition || null;
return this;
}
/** @internal */
_getChange(event, dir) {
const oEvent = this.startEvent;
const newRect = {
width: this.originalRect.width,
height: this.originalRect.height + this.scrolled,
left: this.originalRect.left,
top: this.originalRect.top - this.scrolled
};
const offsetX = event.clientX - oEvent.clientX;
const offsetY = this.sizeToContent ? 0 : event.clientY - oEvent.clientY; // prevent vert resize
let moveLeft;
let moveUp;
if (dir.indexOf('e') > -1) {
newRect.width += offsetX;
}
else if (dir.indexOf('w') > -1) {
newRect.width -= offsetX;
newRect.left += offsetX;
moveLeft = true;
}
if (dir.indexOf('s') > -1) {
newRect.height += offsetY;
}
else if (dir.indexOf('n') > -1) {
newRect.height -= offsetY;
newRect.top += offsetY;
moveUp = true;
}
const constrain = this._constrainSize(newRect.width, newRect.height, moveLeft, moveUp);
if (Math.round(newRect.width) !== Math.round(constrain.width)) { // round to ignore slight round-off errors
if (dir.indexOf('w') > -1) {
newRect.left += newRect.width - constrain.width;
}
newRect.width = constrain.width;
}
if (Math.round(newRect.height) !== Math.round(constrain.height)) {
if (dir.indexOf('n') > -1) {
newRect.top += newRect.height - constrain.height;
}
newRect.height = constrain.height;
}
return newRect;
}
/** @internal constrain the size to the set min/max values */
_constrainSize(oWidth, oHeight, moveLeft, moveUp) {
const o = this.option;
const maxWidth = (moveLeft ? o.maxWidthMoveLeft : o.maxWidth) || Number.MAX_SAFE_INTEGER;
const minWidth = o.minWidth / this.rectScale.x || oWidth;
const maxHeight = (moveUp ? o.maxHeightMoveUp : o.maxHeight) || Number.MAX_SAFE_INTEGER;
const minHeight = o.minHeight / this.rectScale.y || oHeight;
const width = Math.min(maxWidth, Math.max(minWidth, oWidth));
const height = Math.min(maxHeight, Math.max(minHeight, oHeight));
return { width, height };
}
/** @internal */
_applyChange() {
let containmentRect = { left: 0, top: 0, width: 0, height: 0 };
if (this.el.style.position === 'absolute') {
const containmentEl = this.el.parentElement;
const { left, top } = containmentEl.getBoundingClientRect();
containmentRect = { left, top, width: 0, height: 0 };
}
if (!this.temporalRect)
return this;
Object.keys(this.temporalRect).forEach(key => {
const value = this.temporalRect[key];
const scaleReciprocal = key === 'width' || key === 'left' ? this.rectScale.x : key === 'height' || key === 'top' ? this.rectScale.y : 1;
this.el.style[key] = (value - containmentRect[key]) * scaleReciprocal + 'px';
});
return this;
}
/** @internal */
_removeHandlers() {
this.handlers.forEach(handle => handle.destroy());
delete this.handlers;
return this;
}
}
/** @internal */
DDResizable._originStyleProp = ['width', 'height', 'position', 'left', 'top', 'opacity', 'zIndex'];
export { DDResizable };
//# sourceMappingURL=dd-resizable.js.map

Some files were not shown because too many files have changed in this diff Show More