Skip to content

Commit

Permalink
feat: dashboard widget remove button (#7800)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomivirkki authored Sep 16, 2024
1 parent 886742d commit af5c2b1
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 56 deletions.
4 changes: 4 additions & 0 deletions dev/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
console.log('dashboard-item-resize-end');
console.log('item after resize', e.detail);
});

dashboard.addEventListener('dashboard-item-removed', (e) => {
console.log('dashboard-item-removed', e.detail);
});
</script>
</head>

Expand Down
32 changes: 32 additions & 0 deletions packages/dashboard/src/vaadin-dashboard-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license
* Copyright (c) 2016 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/

export const WRAPPER_LOCAL_NAME = 'vaadin-dashboard-widget-wrapper';

/**
* Returns the array of items that contains the given item.
* Might be the dashboard items or the items of a section.
*
* @param {Object} item the item element
* @param {Object[]} items the root level items array
* @return {Object[]} the items array
*/
export function getItemsArrayOfItem(item, items) {
if (items.includes(item)) {
return items;
}
const parentItem = items.find((i) => i.items && getItemsArrayOfItem(item, i.items));
return parentItem ? parentItem.items : null;
}

/**
* Returns the item associated with the given element.
*
* @param {HTMLElement} element the element
*/
export function getElementItem(element) {
return element.closest(WRAPPER_LOCAL_NAME).__item;
}
10 changes: 7 additions & 3 deletions packages/dashboard/src/vaadin-dashboard-section.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,9 @@ class DashboardSection extends ControllerMixin(ElementMixin(PolylitMixin(LitElem
render() {
return html`
<header>
<button id="drag-handle" draggable="true" class="drag-handle" tabindex="-1"></button>
<slot name="title" @slotchange="${this.__onTitleSlotChange}"></slot>
<div id="header-actions">
<span id="drag-handle" draggable="true" class="drag-handle"></span>
</div>
<button id="remove-button" tabindex="-1" @click="${() => this.__remove()}"></button>
</header>
<slot></slot>
Expand Down Expand Up @@ -135,6 +134,11 @@ class DashboardSection extends ControllerMixin(ElementMixin(PolylitMixin(LitElem
__onSectionTitleChanged(sectionTitle) {
this.__titleController.setTitle(sectionTitle);
}

/** @private */
__remove() {
this.dispatchEvent(new CustomEvent('item-remove', { bubbles: true, composed: true }));
}
}

defineCustomElement(DashboardSection);
Expand Down
16 changes: 13 additions & 3 deletions packages/dashboard/src/vaadin-dashboard-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ export const dashboardWidgetAndSectionStyles = css`
align-items: center;
}
#header-actions {
#drag-handle {
display: var(--_vaadin-dashboard-widget-actions-display, none);
font-size: 30px;
cursor: grab;
}
#drag-handle::before {
font-size: 30px;
content: '☰';
cursor: grab;
}
#remove-button {
display: var(--_vaadin-dashboard-widget-actions-display, none);
font-size: 30px;
cursor: pointer;
}
#remove-button::before {
content: '×';
}
`;
22 changes: 13 additions & 9 deletions packages/dashboard/src/vaadin-dashboard-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitEleme
#resize-handle {
display: var(--_vaadin-dashboard-widget-actions-display, none);
}
#resize-handle::before {
position: absolute;
bottom: 0;
right: 0;
font-size: 30px;
content: '\\2921';
cursor: grab;
line-height: 1;
}
#resize-handle::before {
content: '\\2921';
}
:host::after {
content: '';
z-index: 100;
z-index: 2;
position: absolute;
inset-inline-start: 0;
top: 0;
Expand Down Expand Up @@ -96,18 +96,17 @@ class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitEleme
render() {
return html`
<header>
<button id="drag-handle" draggable="true" class="drag-handle" tabindex="-1"></button>
<slot name="title" @slotchange="${this.__onTitleSlotChange}"></slot>
<slot name="header"></slot>
<div id="header-actions">
<span id="drag-handle" draggable="true" class="drag-handle"></span>
</div>
<button id="remove-button" tabindex="-1" @click="${() => this.__remove()}"></button>
</header>
<div id="content">
<slot></slot>
</div>
<div id="resize-handle" class="resize-handle"></div>
<button id="resize-handle" class="resize-handle" tabindex="-1"></button>
`;
}

Expand Down Expand Up @@ -153,6 +152,11 @@ class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitEleme
__updateTitle() {
this.__titleController.setTitle(this.widgetTitle);
}

/** @private */
__remove() {
this.dispatchEvent(new CustomEvent('item-remove', { bubbles: true, composed: true }));
}
}

defineCustomElement(DashboardWidget);
Expand Down
11 changes: 11 additions & 0 deletions packages/dashboard/src/vaadin-dashboard.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ export type DashboardItemDragResizeEvent<TItem extends DashboardItem> = CustomEv
rowspan: number;
}>;

/**
* Fired when an item is removed
*/
export type DashboardItemRemoveEvent<TItem extends DashboardItem> = CustomEvent<{
item: TItem | DashboardSectionItem<TItem>;

items: Array<TItem | DashboardSectionItem<TItem>>;
}>;

export interface DashboardCustomEventMap<TItem extends DashboardItem> {
'dashboard-item-reorder-start': DashboardItemReorderStartEvent;

Expand All @@ -100,6 +109,8 @@ export interface DashboardCustomEventMap<TItem extends DashboardItem> {
'dashboard-item-resize-end': DashboardItemResizeEndEvent<TItem>;

'dashboard-item-drag-resize': DashboardItemDragResizeEvent<TItem>;

'dashboard-item-removed': DashboardItemRemoveEvent<TItem>;
}

export type DashboardEventMap<TItem extends DashboardItem> = DashboardCustomEventMap<TItem> & HTMLElementEventMap;
Expand Down
21 changes: 21 additions & 0 deletions packages/dashboard/src/vaadin-dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
import { css, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { getElementItem, getItemsArrayOfItem } from './vaadin-dashboard-helpers.js';
import { DashboardLayoutMixin } from './vaadin-dashboard-layout-mixin.js';
import { hasWidgetWrappers } from './vaadin-dashboard-styles.js';
import { WidgetReorderController } from './widget-reorder-controller.js';
Expand All @@ -30,6 +31,7 @@ import { WidgetResizeController } from './widget-resize-controller.js';
* @fires {CustomEvent} dashboard-item-drag-resize - Fired when an item will be resized by dragging
* @fires {CustomEvent} dashboard-item-resize-start - Fired when item resizing starts
* @fires {CustomEvent} dashboard-item-resize-end - Fired when item resizing ends
* @fires {CustomEvent} dashboard-item-removed - Fired when an item is removed
*
* @customElement
* @extends HTMLElement
Expand Down Expand Up @@ -108,6 +110,7 @@ class Dashboard extends ControllerMixin(DashboardLayoutMixin(ElementMixin(Themab
super();
this.__widgetReorderController = new WidgetReorderController(this);
this.__widgetResizeController = new WidgetResizeController(this);
this.addEventListener('item-remove', (e) => this.__itemRemove(e));
}

/** @protected */
Expand Down Expand Up @@ -176,6 +179,18 @@ class Dashboard extends ControllerMixin(DashboardLayoutMixin(ElementMixin(Themab
});
}

/** @private */
__itemRemove(e) {
e.stopImmediatePropagation();
const item = getElementItem(e.target);
const items = getItemsArrayOfItem(item, this.items);
items.splice(items.indexOf(item), 1);
this.items = [...this.items];
this.dispatchEvent(
new CustomEvent('dashboard-item-removed', { cancelable: true, detail: { item, items: this.items } }),
);
}

/**
* Fired when item reordering starts
*
Expand Down Expand Up @@ -211,6 +226,12 @@ class Dashboard extends ControllerMixin(DashboardLayoutMixin(ElementMixin(Themab
*
* @event dashboard-item-drag-resize
*/

/**
* Fired when an item is removed
*
* @event dashboard-item-removed
*/
}

defineCustomElement(Dashboard);
Expand Down
38 changes: 7 additions & 31 deletions packages/dashboard/src/widget-reorder-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/

const WRAPPER_LOCAL_NAME = 'vaadin-dashboard-widget-wrapper';
import { getElementItem, getItemsArrayOfItem, WRAPPER_LOCAL_NAME } from './vaadin-dashboard-helpers.js';

const REORDER_EVENT_TIMEOUT = 200;

/**
Expand All @@ -30,7 +31,7 @@ export class WidgetReorderController extends EventTarget {
}

this.__draggedElement = e.target;
this.draggedItem = this.__getElementItem(this.__draggedElement);
this.draggedItem = getElementItem(this.__draggedElement);

// Set the drag image to the dragged element
const { left, top } = this.__draggedElement.getBoundingClientRect();
Expand Down Expand Up @@ -61,7 +62,7 @@ export class WidgetReorderController extends EventTarget {
// Get all elements that are candidates for reordering with the dragged element
const dragContextElements = this.__getDragContextElements(this.__draggedElement);
// Find the up-to-date element instance representing the dragged item
const draggedElement = dragContextElements.find((element) => this.__getElementItem(element) === this.draggedItem);
const draggedElement = dragContextElements.find((element) => getElementItem(element) === this.draggedItem);
if (!draggedElement) {
return;
}
Expand All @@ -82,8 +83,8 @@ export class WidgetReorderController extends EventTarget {
this.__reordering = false;
}, REORDER_EVENT_TIMEOUT);

const targetItem = this.__getElementItem(closestElement);
const targetItems = this.__getItemsArrayOfItem(targetItem);
const targetItem = getElementItem(closestElement);
const targetItems = getItemsArrayOfItem(targetItem, this.host.items);
const targetIndex = targetItems.indexOf(targetItem);

const reorderEvent = new CustomEvent('dashboard-item-drag-reorder', {
Expand Down Expand Up @@ -172,11 +173,6 @@ export class WidgetReorderController extends EventTarget {
}
}

/** @private */
__getElementItem(element) {
return element.closest(WRAPPER_LOCAL_NAME).__item;
}

/**
* Returns the elements (widgets or sections) that are candidates for reordering with the
* currently dragged item. Effectively, this is the list of child widgets or sections inside
Expand All @@ -200,33 +196,13 @@ export class WidgetReorderController extends EventTarget {

/** @private */
__reorderItems(draggedItem, targetIndex) {
const items = this.__getItemsArrayOfItem(draggedItem);
const items = getItemsArrayOfItem(draggedItem, this.host.items);
const draggedIndex = items.indexOf(draggedItem);
items.splice(draggedIndex, 1);
items.splice(targetIndex, 0, draggedItem);
this.host.items = [...this.host.items];
}

/**
* Returns the array of items that contains the given item.
* Might be the host items or the items of a section.
* @private
*/
__getItemsArrayOfItem(item, items = this.host.items) {
for (const i of items) {
if (i === item) {
return items;
}
if (i.items) {
const result = this.__getItemsArrayOfItem(item, i.items);
if (result) {
return result;
}
}
}
return null;
}

/**
* The dragged element might be removed from the DOM during the drag operation if
* the widgets get re-rendered. This method restores the dragged element if it's not
Expand Down
9 changes: 2 additions & 7 deletions packages/dashboard/src/widget-resize-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/

const WRAPPER_LOCAL_NAME = 'vaadin-dashboard-widget-wrapper';
import { addListener } from '@vaadin/component-base/src/gestures.js';
import { getElementItem, WRAPPER_LOCAL_NAME } from './vaadin-dashboard-helpers.js';

/**
* A controller to widget resizing inside a dashboard.
Expand Down Expand Up @@ -38,7 +38,7 @@ export class WidgetResizeController extends EventTarget {
}

this.host.$.grid.toggleAttribute('resizing', true);
this.resizedItem = this.__getElementItem(e.target);
this.resizedItem = getElementItem(e.target);

this.__resizeStartWidth = e.target.offsetWidth;
this.__resizeStartHeight = e.target.offsetHeight;
Expand Down Expand Up @@ -133,11 +133,6 @@ export class WidgetResizeController extends EventTarget {
this.resizedItem = null;
}

/** @private */
__getElementItem(element) {
return element.closest(WRAPPER_LOCAL_NAME).__item;
}

/** @private */
__getItemWrapper(item) {
return [...this.host.querySelectorAll(WRAPPER_LOCAL_NAME)].find((el) => el.__item === item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type TestDashboardItem = DashboardItem & { id: number };

describe('dashboard - widget reordering', () => {
let dashboard: Dashboard<TestDashboardItem>;
const columnWidth = 100;
const columnWidth = 200;

beforeEach(async () => {
dashboard = fixtureSync('<vaadin-dashboard></vaadin-dashboard>');
Expand Down
Loading

0 comments on commit af5c2b1

Please sign in to comment.