Skip to content

Commit

Permalink
refactor: extract board row logic into reusable mixin (#8482)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Jan 10, 2025
1 parent 434846d commit c1a0952
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 227 deletions.
1 change: 1 addition & 0 deletions packages/board/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"polymer"
],
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
"@vaadin/a11y-base": "24.7.0-alpha4",
"@vaadin/component-base": "24.7.0-alpha4"
Expand Down
28 changes: 28 additions & 0 deletions packages/board/src/vaadin-board-row-mixin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license
* Copyright (c) 2000 - 2025 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import type { Constructor } from '@open-wc/dedupe-mixin';
import type { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';

export declare function BoardRowMixin<T extends Constructor<HTMLElement>>(
base: T,
): Constructor<BoardRowMixinClass> & Constructor<ResizeMixinClass> & T;

export declare class BoardRowMixinClass {
/**
* Redraws the row, if necessary.
*
* In most cases, a board row will redraw itself if your reconfigure it.
* If you dynamically change breakpoints
* `--vaadin-board-width-small` or `--vaadin-board-width-medium`,
* then you need to call this method.
*/
redraw(): void;
}
230 changes: 230 additions & 0 deletions packages/board/src/vaadin-board-row-mixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/**
* @license
* Copyright (c) 2000 - 2025 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import { isElementHidden } from '@vaadin/a11y-base/src/focus-utils.js';
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';

const CLASSES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
};

/**
* @polymerMixin
* @mixes ResizeMixin
*/
export const BoardRowMixin = (superClass) =>
class BoardRowMixinClass extends ResizeMixin(superClass) {
constructor() {
super();
this._oldWidth = 0;
this._oldBreakpoints = { smallSize: 600, mediumSize: 960 };
this._oldFlexBasis = [];
}

/** @protected */
ready() {
super.ready();

this.$.insertionPoint.addEventListener('slotchange', () => this.redraw());
}

/** @protected */
connectedCallback() {
super.connectedCallback();
this._onResize();
}

/**
* Adds styles for board row based on width.
* @private
*/
_addStyleNames(width, breakpoints) {
if (width < breakpoints.smallSize) {
this.classList.add(CLASSES.SMALL);
this.classList.remove(CLASSES.MEDIUM);
this.classList.remove(CLASSES.LARGE);
} else if (width < breakpoints.mediumSize) {
this.classList.remove(CLASSES.SMALL);
this.classList.add(CLASSES.MEDIUM);
this.classList.remove(CLASSES.LARGE);
} else {
this.classList.remove(CLASSES.SMALL);
this.classList.remove(CLASSES.MEDIUM);
this.classList.add(CLASSES.LARGE);
}
}

/**
* Calculates flex basis based on colSpan, width and breakpoints.
* @param {number} colSpan colspan value of the row
* @param {number} width width of the row in px
* @param {number} colsInRow number of columns in the row
* @param {object} breakpoints object with smallSize and mediumSize number properties, which tells
* where the row should switch rendering size in pixels.
* @private
*/
_calculateFlexBasis(colSpan, width, colsInRow, breakpoints) {
if (width < breakpoints.smallSize) {
colsInRow = 1;
} else if (width < breakpoints.mediumSize && colsInRow === 4) {
colsInRow = 2;
}
let flexBasis = (colSpan / colsInRow) * 100;
flexBasis = flexBasis > 100 ? 100 : flexBasis;
return `${flexBasis}%`;
}

/** @private */
_reportError() {
const errorMessage = 'The column configuration is not valid; column count should add up to 3 or 4.';
console.warn(errorMessage, `check: \r\n${this.outerHTML}`);
}

/**
* Parses board-cols from DOM.
* If there is not enough space in the row drop board cols.
* @param {!Array<!Node>} nodes array of nodes
* @return {!Array<number>} array of boardCols
* @private
*/
_parseBoardCols(nodes) {
const boardCols = nodes.map((node) => {
if (node.getAttribute('board-cols')) {
return parseInt(node.getAttribute('board-cols'));
}
return 1;
});

let spaceLeft = 4;
let returnBoardCols = [];
nodes.forEach((_node, i) => {
spaceLeft -= boardCols[i];
});

if (spaceLeft < 0) {
this._reportError();
boardCols.forEach((_node, i) => {
returnBoardCols[i] = 1;
});
} else {
returnBoardCols = boardCols.slice(0);
}

return returnBoardCols;
}

/**
* If there is not enough space in the row.
* Extra items are dropped from DOM.
* @param {!Array<number>} boardCols array of board-cols for every node
* @param {!Array<!Node>} nodes array of nodes
* @return {!Array<!Node>} filtered array of nodes
* @private
*/
_removeExtraNodesFromDOM(boardCols, nodes) {
let isErrorReported = false;
let spaceLeft = 4;
const returnNodes = [];
nodes.forEach((node, i) => {
spaceLeft -= boardCols[i];
if (spaceLeft < 0) {
if (!isErrorReported) {
isErrorReported = true;
this._reportError();
}
this.removeChild(node);
} else {
returnNodes[i] = node;
}
});
return returnNodes;
}

/**
* Redraws the row, if necessary.
*
* In most cases, a board row will redraw itself if your reconfigure it.
* If you dynamically change breakpoints
* --vaadin-board-width-small or --vaadin-board-width-medium,
* then you need to call this method.
*/
redraw() {
this._recalculateFlexBasis(true);
}

/**
* @protected
* @override
*/
_onResize() {
this._recalculateFlexBasis(false);
}

/** @private */
_recalculateFlexBasis(forceResize) {
const width = this.getBoundingClientRect().width;
const breakpoints = this._measureBreakpointsInPx();
if (forceResize || this._shouldRecalculate(width, breakpoints)) {
const nodes = this.$.insertionPoint.assignedNodes({ flatten: true });
const filteredNodes = nodes.filter((node) => node.nodeType === Node.ELEMENT_NODE);
this._addStyleNames(width, breakpoints);
const boardCols = this._parseBoardCols(filteredNodes);
const colsInRow = boardCols.reduce((a, b) => a + b, 0);
this._removeExtraNodesFromDOM(boardCols, filteredNodes).forEach((e, i) => {
const newFlexBasis = this._calculateFlexBasis(boardCols[i], width, colsInRow, breakpoints);
if (forceResize || !this._oldFlexBasis[i] || this._oldFlexBasis[i] !== newFlexBasis) {
this._oldFlexBasis[i] = newFlexBasis;
e.style.flexBasis = newFlexBasis;
}
});
this._oldWidth = width;
this._oldBreakpoints = breakpoints;
}
}

/** @private */
_shouldRecalculate(width, breakpoints) {
// Should not recalculate if row is invisible
if (isElementHidden(this)) {
return false;
}
return (
width !== this._oldWidth ||
breakpoints.smallSize !== this._oldBreakpoints.smallSize ||
breakpoints.mediumSize !== this._oldBreakpoints.mediumSize
);
}

/**
* Measure the breakpoints in pixels.
*
* The breakpoints for `small` and `medium` can be given in any unit: `px`, `em`, `in` etc.
* We need to know them in `px` so that they are comparable with the actual size.
*
* @return {object} object with smallSize and mediumSize number properties, which tells
* where the row should switch rendering size in pixels.
* @private
*/
_measureBreakpointsInPx() {
// Convert minWidth to px units for comparison
const breakpoints = {};
const tmpStyleProp = 'background-position';
const smallSize = getComputedStyle(this).getPropertyValue('--small-size');
const mediumSize = getComputedStyle(this).getPropertyValue('--medium-size');
this.style.setProperty(tmpStyleProp, smallSize);
breakpoints.smallSize = parseFloat(getComputedStyle(this).getPropertyValue(tmpStyleProp));
this.style.setProperty(tmpStyleProp, mediumSize);
breakpoints.mediumSize = parseFloat(getComputedStyle(this).getPropertyValue(tmpStyleProp));
this.style.removeProperty(tmpStyleProp);
return breakpoints;
}
};
14 changes: 2 additions & 12 deletions packages/board/src/vaadin-board-row.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* license.
*/
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
import { BoardRowMixin } from './vaadin-board-row-mixin.js';

/**
* `<vaadin-board-row>` is a web component that together with `<vaadin-board>` component allows
Expand Down Expand Up @@ -45,17 +45,7 @@ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
* `--vaadin-board-width-small` | Determines the width where mode changes from `small` to `medium` | `600px`
* `--vaadin-board-width-medium` | Determines the width where mode changes from `medium` to `large` | `960px`
*/
declare class BoardRow extends ResizeMixin(ElementMixin(HTMLElement)) {
/**
* Redraws the row, if necessary.
*
* In most cases, a board row will redraw itself if your reconfigure it.
* If you dynamically change breakpoints
* `--vaadin-board-width-small` or `--vaadin-board-width-medium`,
* then you need to call this method.
*/
redraw(): void;
}
declare class BoardRow extends BoardRowMixin(ElementMixin(HTMLElement)) {}

declare global {
interface HTMLElementTagNameMap {
Expand Down
Loading

0 comments on commit c1a0952

Please sign in to comment.