Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5f2fb18ed | |||
| 9311d494bd |
Binary file not shown.
@@ -59,6 +59,8 @@ function applyThemeOverlay(options: any): any {
|
|||||||
export class ApexChart extends LitElement {
|
export class ApexChart extends LitElement {
|
||||||
private chart?: ApexCharts;
|
private chart?: ApexCharts;
|
||||||
private lastOptions?: any;
|
private lastOptions?: any;
|
||||||
|
private lastOptionsJson?: string;
|
||||||
|
private destroyTimer?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
private readonly onThemeChange = () => {
|
private readonly onThemeChange = () => {
|
||||||
if (!this.chart || !this.lastOptions) return;
|
if (!this.chart || !this.lastOptions) return;
|
||||||
@@ -71,7 +73,14 @@ export class ApexChart extends LitElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
clearTimeout(this.destroyTimer);
|
||||||
|
this.destroyTimer = undefined;
|
||||||
window.addEventListener('dialect-theme-change', this.onThemeChange);
|
window.addEventListener('dialect-theme-change', this.onThemeChange);
|
||||||
|
// Back after a real teardown (Flow detach, cached view): rebuild, since
|
||||||
|
// the server only pushes options on setData(), not on re-attach.
|
||||||
|
if (!this.chart && this.lastOptionsJson) {
|
||||||
|
void this.renderChart(this.lastOptionsJson);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -80,6 +89,7 @@ export class ApexChart extends LitElement {
|
|||||||
|
|
||||||
async renderChart(optionsJson: string) {
|
async renderChart(optionsJson: string) {
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
this.lastOptionsJson = optionsJson;
|
||||||
const options = JSON.parse(optionsJson);
|
const options = JSON.parse(optionsJson);
|
||||||
|
|
||||||
options.chart = {
|
options.chart = {
|
||||||
@@ -106,6 +116,15 @@ export class ApexChart extends LitElement {
|
|||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
window.removeEventListener('dialect-theme-change', this.onThemeChange);
|
window.removeEventListener('dialect-theme-change', this.onThemeChange);
|
||||||
|
// gridstack's _sortDom() re-appends item elements after every
|
||||||
|
// move/resize, which disconnects and immediately reconnects this
|
||||||
|
// element within the same task — destroying the chart synchronously
|
||||||
|
// would blank it with nothing to trigger a re-render. Defer past the
|
||||||
|
// current task and bail out if we came back.
|
||||||
|
this.destroyTimer = setTimeout(() => {
|
||||||
|
if (this.isConnected) return;
|
||||||
this.chart?.destroy();
|
this.chart?.destroy();
|
||||||
|
this.chart = undefined;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,8 @@ public enum Fa {
|
|||||||
GRID("fa-solid", "fa-table-cells-large"),
|
GRID("fa-solid", "fa-table-cells-large"),
|
||||||
ADD("fa-solid", "fa-plus"),
|
ADD("fa-solid", "fa-plus"),
|
||||||
REMOVE("fa-solid", "fa-trash"),
|
REMOVE("fa-solid", "fa-trash"),
|
||||||
RESET("fa-solid", "fa-arrow-rotate-left");
|
RESET("fa-solid", "fa-arrow-rotate-left"),
|
||||||
|
DRAG("fa-solid", "fa-grip-vertical");
|
||||||
|
|
||||||
private final String[] classes;
|
private final String[] classes;
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,19 @@ import java.util.UUID;
|
|||||||
* A single cell inside a {@link GridStackLayout}. Position/size are written as
|
* A single cell inside a {@link GridStackLayout}. Position/size are written as
|
||||||
* {@code gs-*} attributes, which is how gridstack.js reads a widget's initial
|
* {@code gs-*} attributes, which is how gridstack.js reads a widget's initial
|
||||||
* placement when {@code makeWidget} converts it client-side (see grid-stack.ts).
|
* placement when {@code makeWidget} converts it client-side (see grid-stack.ts).
|
||||||
|
* <p>
|
||||||
|
* Dragging starts from the grip handle only, never from the item body, so the
|
||||||
|
* components inside an item stay interactive (see
|
||||||
|
* {@link GridStackLayout#setDragHandle(String)}).
|
||||||
*/
|
*/
|
||||||
public class GridStackItem extends Div {
|
public class GridStackItem extends Div {
|
||||||
|
|
||||||
|
/** Selector gridstack is told to treat as the drag handle — see
|
||||||
|
* {@link GridStackLayout}'s {@code handle} option. */
|
||||||
|
public static final String DRAG_HANDLE_CLASS = "dialect-drag-handle";
|
||||||
|
|
||||||
private final Div content = new Div();
|
private final Div content = new Div();
|
||||||
|
private final Div dragHandle = new Div();
|
||||||
|
|
||||||
public GridStackItem(Component... content) {
|
public GridStackItem(Component... content) {
|
||||||
this(UUID.randomUUID().toString(), 0, 0, 4, 3, content);
|
this(UUID.randomUUID().toString(), 0, 0, 4, 3, content);
|
||||||
@@ -33,6 +42,15 @@ public class GridStackItem extends Div {
|
|||||||
// own child.
|
// own child.
|
||||||
getElement().appendChild(this.content.getElement());
|
getElement().appendChild(this.content.getElement());
|
||||||
this.content.add(content);
|
this.content.add(content);
|
||||||
|
|
||||||
|
// Sibling of the content wrapper, not a child of it: the wrapper is
|
||||||
|
// `overflow: auto`, so a handle inside it would scroll out of view.
|
||||||
|
dragHandle.addClassName(DRAG_HANDLE_CLASS);
|
||||||
|
dragHandle.add(Fa.DRAG.create());
|
||||||
|
String label = getTranslation("gridstack.dragHandle");
|
||||||
|
dragHandle.getElement().setAttribute("title", label);
|
||||||
|
dragHandle.getElement().setAttribute("aria-label", label);
|
||||||
|
getElement().appendChild(dragHandle.getElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(Component... components) {
|
public void add(Component... components) {
|
||||||
@@ -52,11 +70,20 @@ public class GridStackItem extends Div {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Also hides the grip handle when {@code false} — the {@code gs-no-move}
|
||||||
|
* attribute written here is what {@code styles.css} keys the handle's
|
||||||
|
* visibility off. */
|
||||||
public GridStackItem setMovable(boolean movable) {
|
public GridStackItem setMovable(boolean movable) {
|
||||||
setBooleanAttribute("gs-no-move", !movable);
|
setBooleanAttribute("gs-no-move", !movable);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The element gridstack drags the item by. Exposed so callers can restyle
|
||||||
|
* or reposition it; removing it from the DOM makes the item immovable. */
|
||||||
|
public Div getDragHandle() {
|
||||||
|
return dragHandle;
|
||||||
|
}
|
||||||
|
|
||||||
/** Writes an explicit "true" (gridstack reads this via its own
|
/** Writes an explicit "true" (gridstack reads this via its own
|
||||||
* {@code Utils.toBool}) or removes the attribute — avoids relying on
|
* {@code Utils.toBool}) or removes the attribute — avoids relying on
|
||||||
* Vaadin's HTML5 empty-string boolean-attribute convention, which
|
* Vaadin's HTML5 empty-string boolean-attribute convention, which
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public class GridStackLayout extends Component implements HasSize, HasStyle {
|
|||||||
options.put("minRow", 1);
|
options.put("minRow", 1);
|
||||||
options.put("float", true);
|
options.put("float", true);
|
||||||
options.put("animate", true);
|
options.put("animate", true);
|
||||||
|
// gridstack's default handle is `.grid-stack-item-content`, i.e. the
|
||||||
|
// whole widget, which swallows clicks meant for the components inside
|
||||||
|
// it. Restrict dragging to the grip each GridStackItem renders.
|
||||||
|
options.put("handle", "." + GridStackItem.DRAG_HANDLE_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GridStackLayout setColumn(int column) {
|
public GridStackLayout setColumn(int column) {
|
||||||
@@ -85,6 +89,14 @@ public class GridStackLayout extends Component implements HasSize, HasStyle {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Overrides the selector items are dragged by (default: the grip rendered
|
||||||
|
* by {@link GridStackItem}). The selector must match an element inside the
|
||||||
|
* item. */
|
||||||
|
public GridStackLayout setDragHandle(String selector) {
|
||||||
|
options.put("handle", selector);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public GridStackLayout setStaticGrid(boolean staticGrid) {
|
public GridStackLayout setStaticGrid(boolean staticGrid) {
|
||||||
options.put("staticGrid", staticGrid);
|
options.put("staticGrid", staticGrid);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -146,6 +146,47 @@ vaadin-app-layout::part(content) {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Drag grip: gridstack drags items by this element only (`handle` option in
|
||||||
|
GridStackLayout), so the item body keeps its own interactivity. The item
|
||||||
|
itself is absolutely positioned by gridstack, so it is the offset parent. */
|
||||||
|
.dialect-drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--dialect-ink);
|
||||||
|
opacity: 0;
|
||||||
|
cursor: grab;
|
||||||
|
transition: opacity 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-stack-item:hover > .dialect-drag-handle,
|
||||||
|
.dialect-drag-handle:focus-visible {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialect-drag-handle:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch devices never hover — keep the grip permanently visible there. */
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
.dialect-drag-handle {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-stack-item[gs-no-move] > .dialect-drag-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-stack-item-content .ui-resizable-handle {
|
.grid-stack-item-content .ui-resizable-handle {
|
||||||
background: var(--dialect-primary);
|
background: var(--dialect-primary);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ card.registration=Registrierung
|
|||||||
card.employees=Mitarbeiter
|
card.employees=Mitarbeiter
|
||||||
card.gridstackHint=Bedienung
|
card.gridstackHint=Bedienung
|
||||||
|
|
||||||
gridstack.hint=Karten am Titel greifen und verschieben, an der unteren rechten Ecke die Größe ändern. Das Layout wird im Browser gespeichert und beim nächsten Besuch wiederhergestellt.
|
gridstack.hint=Karten am Griff oben rechts verschieben, an der unteren rechten Ecke die Größe ändern. Das Layout wird im Browser gespeichert und beim nächsten Besuch wiederhergestellt.
|
||||||
gridstack.addWidget=Widget hinzufügen
|
gridstack.addWidget=Widget hinzufügen
|
||||||
gridstack.removeWidget=Letztes entfernen
|
gridstack.removeWidget=Letztes entfernen
|
||||||
gridstack.reset=Layout zurücksetzen
|
gridstack.reset=Layout zurücksetzen
|
||||||
@@ -32,6 +32,7 @@ gridstack.status=Layout geändert – {0} Widgets
|
|||||||
gridstack.statusInitial=Layout unverändert
|
gridstack.statusInitial=Layout unverändert
|
||||||
gridstack.widget=Widget {0}
|
gridstack.widget=Widget {0}
|
||||||
gridstack.widgetText=Frei platzierbare Karte.
|
gridstack.widgetText=Frei platzierbare Karte.
|
||||||
|
gridstack.dragHandle=Verschieben
|
||||||
|
|
||||||
chart.revenueSeries=Umsatz 2026
|
chart.revenueSeries=Umsatz 2026
|
||||||
chart.pointClick=Serie {0}, Punkt {1}
|
chart.pointClick=Serie {0}, Punkt {1}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ card.registration=Registration
|
|||||||
card.employees=Employees
|
card.employees=Employees
|
||||||
card.gridstackHint=How it works
|
card.gridstackHint=How it works
|
||||||
|
|
||||||
gridstack.hint=Drag cards by their header to move them, resize them from the bottom right corner. The layout is stored in your browser and restored on your next visit.
|
gridstack.hint=Move cards with the grip in their top right corner, resize them from the bottom right corner. The layout is stored in your browser and restored on your next visit.
|
||||||
gridstack.addWidget=Add widget
|
gridstack.addWidget=Add widget
|
||||||
gridstack.removeWidget=Remove last
|
gridstack.removeWidget=Remove last
|
||||||
gridstack.reset=Reset layout
|
gridstack.reset=Reset layout
|
||||||
@@ -32,6 +32,7 @@ gridstack.status=Layout changed – {0} widgets
|
|||||||
gridstack.statusInitial=Layout unchanged
|
gridstack.statusInitial=Layout unchanged
|
||||||
gridstack.widget=Widget {0}
|
gridstack.widget=Widget {0}
|
||||||
gridstack.widgetText=Freely placeable card.
|
gridstack.widgetText=Freely placeable card.
|
||||||
|
gridstack.dragHandle=Move
|
||||||
|
|
||||||
chart.revenueSeries=Revenue 2026
|
chart.revenueSeries=Revenue 2026
|
||||||
chart.pointClick=Series {0}, point {1}
|
chart.pointClick=Series {0}, point {1}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ card.registration=Registro
|
|||||||
card.employees=Empleados
|
card.employees=Empleados
|
||||||
card.gridstackHint=Cómo funciona
|
card.gridstackHint=Cómo funciona
|
||||||
|
|
||||||
gridstack.hint=Arrastra las tarjetas por su título para moverlas y cambia su tamaño desde la esquina inferior derecha. El diseño se guarda en el navegador y se restaura en la próxima visita.
|
gridstack.hint=Mueve las tarjetas con el asa de la esquina superior derecha y cambia su tamaño desde la esquina inferior derecha. El diseño se guarda en el navegador y se restaura en la próxima visita.
|
||||||
gridstack.addWidget=Añadir widget
|
gridstack.addWidget=Añadir widget
|
||||||
gridstack.removeWidget=Quitar el último
|
gridstack.removeWidget=Quitar el último
|
||||||
gridstack.reset=Restablecer diseño
|
gridstack.reset=Restablecer diseño
|
||||||
@@ -32,6 +32,7 @@ gridstack.status=Diseño modificado – {0} widgets
|
|||||||
gridstack.statusInitial=Diseño sin cambios
|
gridstack.statusInitial=Diseño sin cambios
|
||||||
gridstack.widget=Widget {0}
|
gridstack.widget=Widget {0}
|
||||||
gridstack.widgetText=Tarjeta de colocación libre.
|
gridstack.widgetText=Tarjeta de colocación libre.
|
||||||
|
gridstack.dragHandle=Mover
|
||||||
|
|
||||||
chart.revenueSeries=Ingresos 2026
|
chart.revenueSeries=Ingresos 2026
|
||||||
chart.pointClick=Serie {0}, punto {1}
|
chart.pointClick=Serie {0}, punto {1}
|
||||||
|
|||||||
Reference in New Issue
Block a user