added gridstack
This commit is contained in:
@@ -32,6 +32,8 @@ Vaadin Flow (server-side Java UI, no hand-written HTML/JS for views) on Spring B
|
||||
|
||||
Since `apex-chart.ts` renders into light DOM (`createRenderRoot()` returns `this`), global CSS can reach into the chart markup — but series/legend/grid colors are driven entirely by the options JSON, not CSS, because ApexCharts renders its own SVG/canvas.
|
||||
|
||||
**`GridStackLayout`/`GridStackItem`** (`components/`) wrap [gridstack.js](https://gridstack.js.org) 13.1.0 for draggable/resizable grids. Unlike `ApexChart`, the bridge (`frontend/components/grid-stack.ts`) is a plain `HTMLElement`, not a Lit component — its children are server-rendered `GridStackItem`s living in light DOM, and a Lit render root would fight gridstack for ownership of them. A `MutationObserver` calls `makeWidget`/`removeWidget` as Flow adds/removes children, so there's no explicit add/remove protocol to the client. Every `GridStackItem` needs a stable `gs-id` (auto-generated if not given) — `GridStackLayout.setStorageKey(...)` persists drag/resize state to browser `localStorage` keyed on it and restores by matching ids, so items lose their saved position if their id changes between reloads. Item styling (`.grid-stack-item-content`) extends the `--dialect-*` alias layer in `styles.css`, same as `.dialect-card`.
|
||||
|
||||
**Views** (`views/`): `MainLayout` (`@Layout`, applies to all routes) is the `AppLayout` shell — navbar + `SideNav` drawer, with one `SideNavItem` per route. Routes: `DashboardView` (`@Route("")`) wraps each chart in a `Card` (`components/Card.java`); `FormView` (`@Route("formular")`) demonstrates form controls bound via `Binder`; `TableView` (`@Route("tabelle")`) demonstrates a `Grid` over dummy data with a live text filter (`GridListDataView.addFilter`, `TextField` in `ValueChangeMode.EAGER`). New views should reuse `Card` to wrap their content rather than adding components directly, and get a matching `SideNavItem` in `MainLayout`.
|
||||
|
||||
**Styling**: `src/main/resources/META-INF/resources/styles.css` is the one project-level stylesheet (loaded via `@StyleSheet("styles.css")` in `Application.java`). It defines `--dialect-*` design tokens (Dialect design system: primary orange `#E86C00`, cool-gray background, card radius/shadow) and aliases them onto Aura's own CSS custom properties (`--aura-accent-color-*`, `--aura-background-color-*`, `--aura-orange`, `--aura-yellow`) rather than fighting the theme. Aura tokens use OKLCH + relative-color syntax and accept plain hex overrides. When restyling, prefer extending this alias layer over hardcoding new colors in components.
|
||||
|
||||
Generated
+17
@@ -18,6 +18,7 @@
|
||||
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||
"apexcharts": "5.15.2",
|
||||
"date-fns": "4.1.0",
|
||||
"gridstack": "13.1.0",
|
||||
"lit": "3.3.3",
|
||||
"ol": "10.6.1",
|
||||
"proj4": "2.17.0",
|
||||
@@ -5304,6 +5305,22 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/gridstack": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gridstack/-/gridstack-13.1.0.tgz",
|
||||
"integrity": "sha512-cOGK9Ksy0mzTOTbEI32vpZdCby2WbzbnZcYxgASopkJWg8beBpp7gRhiTNROUtTHbDansXNOxoCfkyRTlx5DNw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://www.paypal.me/alaind831"
|
||||
},
|
||||
{
|
||||
"type": "venmo",
|
||||
"url": "https://www.venmo.com/adumesny"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
|
||||
+6
-2
@@ -14,6 +14,7 @@
|
||||
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||
"apexcharts": "5.15.2",
|
||||
"date-fns": "4.1.0",
|
||||
"gridstack": "13.1.0",
|
||||
"lit": "3.3.3",
|
||||
"ol": "10.6.1",
|
||||
"proj4": "2.17.0",
|
||||
@@ -61,6 +62,7 @@
|
||||
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||
"apexcharts": "5.15.2",
|
||||
"date-fns": "4.1.0",
|
||||
"gridstack": "13.1.0",
|
||||
"lit": "3.3.3",
|
||||
"ol": "10.6.1",
|
||||
"proj4": "2.17.0",
|
||||
@@ -95,7 +97,7 @@
|
||||
"workbox-routing": "7.4.1",
|
||||
"workbox-strategies": "7.4.1"
|
||||
},
|
||||
"hash": "54350847a380a8c1b6cc526f6f7eff8dd9253d848e38919e7e1b633cec9f3aca",
|
||||
"hash": "a8466446482b2201dfcaacaf427dabc79f870e7716e2d8f65cf31eca3870ef6a",
|
||||
"overrides": {
|
||||
"@fortawesome/fontawesome-free": "$@fortawesome/fontawesome-free",
|
||||
"@vaadin/a11y-base": "25.2.0",
|
||||
@@ -177,6 +179,7 @@
|
||||
"@vaadin/virtual-list": "25.2.0",
|
||||
"apexcharts": "$apexcharts",
|
||||
"date-fns": "$date-fns",
|
||||
"gridstack": "$gridstack",
|
||||
"lit": "$lit",
|
||||
"ol": "$ol",
|
||||
"proj4": "$proj4",
|
||||
@@ -277,6 +280,7 @@
|
||||
"glob": "13.0.6"
|
||||
},
|
||||
"apexcharts": "$apexcharts",
|
||||
"@fortawesome/fontawesome-free": "$@fortawesome/fontawesome-free"
|
||||
"@fortawesome/fontawesome-free": "$@fortawesome/fontawesome-free",
|
||||
"gridstack": "$gridstack"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -34,13 +34,15 @@ class GridStackLayout extends HTMLElement {
|
||||
|
||||
this.classList.add('grid-stack');
|
||||
const options = JSON.parse(optionsJson);
|
||||
this.grid = GridStack.init(options, this);
|
||||
const grid = GridStack.init(options, this);
|
||||
if (!grid) return;
|
||||
this.grid = grid;
|
||||
|
||||
this.registerChildren();
|
||||
this.restore();
|
||||
|
||||
this.grid.on('change added removed', () => this.schedulePersist());
|
||||
this.grid.on('resizestop', () => window.dispatchEvent(new Event('resize')));
|
||||
grid.on('change added removed', () => this.schedulePersist());
|
||||
grid.on('resizestop', () => window.dispatchEvent(new Event('resize')));
|
||||
}
|
||||
|
||||
/** Drops any saved layout for this grid and re-applies the positions
|
||||
@@ -73,6 +75,15 @@ class GridStackLayout extends HTMLElement {
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (!(node instanceof HTMLElement)) return;
|
||||
if (!(node as any).gridstackNode) return;
|
||||
// gridstack's own _sortDom() re-appends children (within this
|
||||
// same element) to match layout order after every
|
||||
// move/resize, which fires childList mutations too. A
|
||||
// reordered node is back in the (still connected) DOM by the
|
||||
// time this callback runs; only a genuinely removed node is
|
||||
// disconnected. Without this check, removeWidget() here
|
||||
// would fire on gridstack's own reorder, re-triggering
|
||||
// _sortDom() and looping forever.
|
||||
if (node.isConnected) return;
|
||||
this.grid!.removeWidget(node, false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ public class GridStackItem extends Div {
|
||||
getElement().setAttribute("gs-h", String.valueOf(h));
|
||||
|
||||
this.content.addClassName("grid-stack-item-content");
|
||||
add(this.content);
|
||||
// Attach the wrapper div directly through the element API — calling
|
||||
// the overridden add(Component...) below would route it through
|
||||
// `content.add(...)`, making the wrapper try to add itself as its
|
||||
// own child.
|
||||
getElement().appendChild(this.content.getElement());
|
||||
this.content.add(content);
|
||||
}
|
||||
|
||||
@@ -80,7 +84,7 @@ public class GridStackItem extends Div {
|
||||
}
|
||||
|
||||
/** Snapshot of a widget's grid placement, as reported by the client after a
|
||||
* drag/resize (see {@link GridStackLayout#onLayoutChange(String)}). */
|
||||
* drag/resize. */
|
||||
public record Position(String id, int x, int y, int w, int h) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,32 @@ vaadin-app-layout::part(content) {
|
||||
font-weight: 600;
|
||||
color: var(--dialect-ink);
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* GridStackLayout (components/GridStackLayout.java): extend the same alias
|
||||
layer used by .dialect-card rather than hardcoding new colors. */
|
||||
.grid-stack-item-content {
|
||||
background: var(--dialect-surface);
|
||||
border-radius: var(--dialect-radius);
|
||||
box-shadow: var(--dialect-shadow);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* A Card placed inside a grid item should fill the item instead of
|
||||
stacking its own surface/shadow on top of grid-stack-item-content's. */
|
||||
.grid-stack-item-content > .dialect-card {
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.grid-stack-item-content .ui-resizable-handle {
|
||||
background: var(--dialect-primary);
|
||||
opacity: 0.6;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.grid-stack-placeholder > .placeholder-content {
|
||||
background: var(--dialect-bg);
|
||||
border: 2px dashed var(--dialect-border);
|
||||
border-radius: var(--dialect-radius);
|
||||
}
|
||||
Reference in New Issue
Block a user