Closing a widget was instant and destructive - one misclick on the X
lost its size and position. Close now shows a toast with an undo
button that re-adds the very same widget at its captured gs-x/y/w/h,
keeping its gs-id. Programmatic close() (fromClient=false) is skipped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGRChzremwYCUctf2qMrQz
The ApexCharts toolbar stays hidden (DialectTheme), so the dashboard had no
export at all. Adds an "Als CSV exportieren" entry to the per-widget action
menu, served server-side from the same data the widget renders.
- CsvExport: the export table plus its CSV dialect (';' separator, CRLF,
UTF-8 BOM, locale-formatted numbers) and file-name slugging.
- GridStackItem.setActionDownload: turns a menu entry's caption into an
anchor over a DownloadHandler, so an entry can hand out a file.
- WidgetRegistry: keeps each widget's query, not only its result, so the
Export hook re-runs it — an export always matches the current filter.
- DashboardView: builds the download on click; file name is widget title +
period (revenue-trend-half-year.csv).
Chart image export is left as the follow-up the issue calls optional.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
A GridStackItem now carries an overflow menu next to the grip and the
close button: refresh, maximize, duplicate, remove. Maximize and remove
are handled by the item itself; refresh and duplicate are only reported,
since what they mean depends on the widget.
Maximizing only sets a class — the item's gs-* attributes and its
gridstack node are untouched, so restoring is by definition the position
it had.
The menu sits outside the drag-handle selector, like the close button, so
opening it never starts a drag; a Playwright test drags it to prove it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
Every GridStackItem now renders a close button next to its drag grip,
mirroring the grip's hover affordance. Closing detaches the item
server-side; grid-stack.ts already unregisters widgets via its
MutationObserver, so the layout is persisted without an extra protocol.
Closable is on by default and can be turned off per item with
setClosable(false); GridStackItem.CloseEvent lets views react.
The GridStackView "remove last" toolbar button is dropped — per-widget
close replaces it, so the view no longer tracks a widget stack. Its
counter now only grows, keeping a closed widget's id from being handed
to a new widget (which would inherit the saved position).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue9ZtWUBQF4SuSHpzZ3zwq