Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(addon/components/paper-grid): converts to glimmer components. #1299

Open
wants to merge 16 commits into
base: feature/glimmer-paper-button
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ee3746e
feat(addon/components): converts `paper-grid-tile-footer` to a glimme…
matthewhartstonge Nov 7, 2024
e484119
refactor(addon/utils): migrates to es5 getters.
matthewhartstonge Nov 8, 2024
e5c53c2
feat(addon/components): converts `paper-grid-tile` to a glimmer compo…
matthewhartstonge Nov 8, 2024
d4b42aa
feat(addon/components): converts `paper-grid-list` to a glimmer compo…
matthewhartstonge Nov 8, 2024
7f73ec4
chore(addon/components/paper-grid-tile): removes `@rowHeight` from `d…
matthewhartstonge Nov 10, 2024
8437f9a
fix(addon/components/paper-grid-tile): fixes tile registration ordering.
matthewhartstonge Nov 10, 2024
64c1368
fix(addon/components/paper-grid-list): fixes grid calculations based …
matthewhartstonge Nov 10, 2024
36f108e
feat(addon/utils): adds request animation frame utilities.
matthewhartstonge Nov 10, 2024
ab026bc
fix(addon/components/paper-grid-list): fixes and simplifies rAF debou…
matthewhartstonge Nov 10, 2024
0d2cfb8
refactor(addon/components/paper-grid-list): changes `currentMedia` to…
matthewhartstonge Nov 11, 2024
0d8bc9b
ci(tests/integration/components/paper-grid-list): migrate from ember …
matthewhartstonge Nov 11, 2024
02aae93
refactor(addon/components/paper-grid-list): refactors to use notifier…
matthewhartstonge Nov 11, 2024
a9f8653
feat(addon/modifiers): adds `mutation-observer` modifier for reportin…
matthewhartstonge Nov 11, 2024
8bf2ea0
fix(addon/components/paper-grid-list): adds `mutation-observer` to de…
matthewhartstonge Nov 11, 2024
cccda76
docs(addon/components/paper-grid-list): fixes mislabelled param types.
matthewhartstonge Nov 11, 2024
05d7c66
docs(addon/components/paper-grid): documents didInsertNode.
matthewhartstonge Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions addon/components/paper-grid-list.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{{yield (hash
tile=(component "paper-grid-tile")
)}}
<md-grid-list
class={{@class}}
{{did-insert this.didInsertNode}}
{{did-update this.didUpdateNode @cols @gutter @rowHeight}}
{{mutation-observer this.didUpdateNode}}
...attributes
>
{{yield (hash tile=(component 'paper-grid-tile' parent=this))}}
</md-grid-list>
260 changes: 185 additions & 75 deletions addon/components/paper-grid-list.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
/* eslint-disable ember/classic-decorator-no-classic-methods, ember/no-classic-components, ember/no-computed-properties-in-native-classes, ember/no-get */
/**
* @module ember-paper
*/
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

import { tagName } from '@ember-decorators/component';
import Component from '@ember/component';
import { computed } from '@ember/object';
import { bind, debounce } from '@ember/runloop';
import { ParentMixin } from 'ember-composability-tools';
import gridLayout from '../utils/grid-layout';
import { invokeAction } from 'ember-paper/utils/invoke-action';
import { debounce } from '../utils/raf';

const mediaRegex = /(^|\s)((?:print-)|(?:[a-z]{2}-){1,2})?(\d+)(?!\S)/g;
const rowHeightRegex =
Expand Down Expand Up @@ -38,86 +34,160 @@ const applyStyles = (el, styles) => {
}
};

@tagName('md-grid-list')
export default class PaperGridList extends Component.extend(ParentMixin) {
/**
* A responsive grid list component that arranges child tiles in a configurable grid layout
*
* @class PaperGridList
* @extends Component
* @arg {string} class
* @arg {string} cols
* @arg {string} gutter
* @arg {string} rowHeight
*/
export default class PaperGridList extends Component {
/**
* Service containing media query breakpoints and constants
*/
@service constants;

get tiles() {
return this.childComponents;
/**
* Set of child grid tile components
* @type {Set<PaperGridTile>}
*/
@tracked children;
/**
* Set of callbacks to notify children when they need to update their position.
* @type {Set<Function>}
*/
@tracked childrenNotifyUpdate;
/**
* Reference to the component's DOM element
* @type {HTMLElement}
*/
@tracked element;
/**
* Map of media query listener instances
* @type {Object}
*/
@tracked listenerList = {};
/**
* Map of media query change handler functions
* @type {Object}
*/
@tracked listeners = {};
/**
* Map of active media query states
* @type {Object}
*/
@tracked media = {};
/**
* RAF ID for debouncing grid updates
* @type {number}
*/
@tracked rafUpdateGrid;
/**
* Number of rows in the grid
* @type {number}
*/
@tracked rowCount;

constructor() {
super(...arguments);

this.children = new Set();
this.childrenNotifyUpdate = new Set();
}

didInsertElement() {
super.didInsertElement(...arguments);
/**
* Performs any required DOM setup.
* @param {HTMLElement} element
*/
@action didInsertNode(element) {
this.element = element;
this._installMediaListener();
}

didUpdate() {
super.didUpdate(...arguments);

// Debounces until the next run loop
debounce(this, this.updateGrid, 0);
@action didUpdateNode() {
this.updateGrid();
}

willDestroyElement() {
super.willDestroyElement(...arguments);
willDestroy() {
super.willDestroy(...arguments);
this._uninstallMediaListener();
}

/**
* Registers a child tile component
* @param {PaperGridTile} tile - The tile component to register
* @param {Function} notifyUpdate - A callback to notify children on when they should update.
*/
@action registerChild(tile, notifyUpdate) {
this.children.add(tile);
this.childrenNotifyUpdate.add(notifyUpdate);
this.updateGrid();
}

/**
* Unregisters a child tile component
* @param {PaperGridTile} tile - The tile component to unregister
* @param {Function} notifyUpdate - The notify callback to remove.
*/
@action unregisterChild(tile, notifyUpdate) {
this.children.delete(tile);
this.childrenNotifyUpdate.delete(notifyUpdate);
this.updateGrid();
}

// Sets up a listener for each media query
_installMediaListener() {
for (let mediaName in this.get('constants.MEDIA')) {
let query = this.get('constants.MEDIA')[mediaName] || media(mediaName);
for (let mediaName in this.constants.MEDIA) {
let query = this.constants.MEDIA[mediaName] || media(mediaName);
let mediaList = window.matchMedia(query);
let listenerName = mediaListenerName(mediaName);

// Sets mediaList to a property so removeListener can access it
this.set(`${listenerName}List`, mediaList);
this.listenerList[`${listenerName}List`] = mediaList;

// Creates a function based on mediaName so that removeListener can remove it.
this.set(
listenerName,
bind(this, (result) => {
this._mediaDidChange(mediaName, result.matches);
})
);
let onchange = (result) => {
this._mediaDidChange(mediaName, result.matches);
};
this.listeners[listenerName] = onchange.bind(this);

// Trigger initial grid calculations
this._mediaDidChange(mediaName, mediaList.matches);

mediaList.addListener(this[listenerName]);
mediaList.addListener(this.listeners[listenerName]);
}
}

_uninstallMediaListener() {
for (let mediaName in this.get('constants.MEDIA')) {
for (let mediaName in this.constants.MEDIA) {
let listenerName = mediaListenerName(mediaName);
let mediaList = this.get(`${listenerName}List`);
mediaList.removeListener(this[listenerName]);
let mediaList = this.listenerList[`${listenerName}List`];
if (mediaList) {
mediaList.removeListener(this.listeners[listenerName]);
}
}
}

_mediaDidChange(mediaName, matches) {
this.set(mediaName, matches);

// Debounces until the next run loop
debounce(this, this._updateCurrentMedia, 0);
}

_updateCurrentMedia() {
let mediaPriorities = this.get('constants.MEDIA_PRIORITY');
let currentMedia = mediaPriorities.filter((mediaName) =>
this.get(mediaName)
);
this.set('currentMedia', currentMedia);
this.media[mediaName] = matches;
this.updateGrid();
}

// Updates styles and triggers onUpdate callbacks
updateGrid() {
applyStyles(this.element, this._gridStyle());
// Debounce until the next frame
const updateGrid = () => {
applyStyles(this.element, this._gridStyle());
this.childrenNotifyUpdate.forEach((notify) => notify());
if (this.args.onUpdate) {
this.args.onUpdate();
}
};

this.tiles.forEach((tile) => tile.updateTile());
invokeAction(this, 'onUpdate');
this.rafUpdateGrid = debounce(this.rafUpdateGrid, updateGrid);
}

_gridStyle() {
Expand Down Expand Up @@ -171,26 +241,28 @@ export default class PaperGridList extends Component.extend(ParentMixin) {

// Calculates tile positions
_setTileLayout() {
let tiles = this.orderedTiles();
let tiles = this.orderedTiles;
let layoutInfo = gridLayout(this.currentCols, tiles);

tiles.forEach((tile, i) => tile.set('position', layoutInfo.positions[i]));
tiles.forEach((tile, i) => {
tile.position = layoutInfo.positions[i];
});
matthewhartstonge marked this conversation as resolved.
Show resolved Hide resolved

this.set('rowCount', layoutInfo.rowCount);
this.rowCount = layoutInfo.rowCount;
}

// Sorts tiles by their order in the dom
orderedTiles() {
/**
* Returns child tiles sorted by DOM order
* @type {Array<PaperGridTile>}
*/
get orderedTiles() {
// Convert NodeList to native javascript array, to be able to use indexOf.
let domTiles = Array.prototype.slice.call(
this.element.querySelectorAll('md-grid-tile')
);

return this.tiles.sort((a, b) => {
return domTiles.indexOf(a.get('element')) >
domTiles.indexOf(b.get('element'))
? 1
: -1;
return Array.from(this.children).sort((a, b) => {
return domTiles.indexOf(a.element) > domTiles.indexOf(b.element) ? 1 : -1;
});
}

Expand Down Expand Up @@ -218,36 +290,60 @@ export default class PaperGridList extends Component.extend(ParentMixin) {
return sizes.base;
}

@computed('cols')
/**
* Returns the parsed responsive column sizes
* @type {Object<string,number>}
*/
get colsMedia() {
let sizes = this._extractResponsiveSizes(this.cols);
let sizes = this._extractResponsiveSizes(this.args.cols);
if (Object.keys(sizes).length === 0) {
throw new Error('md-grid-list: No valid cols found');
}
return sizes;
}

@computed('colsMedia', 'currentMedia.[]')
/**
* Returns the currently active media query breakpoints
* @type {Array<string>}
*/
get currentMedia() {
let mediaPriorities = this.constants.MEDIA_PRIORITY;
return mediaPriorities.filter((mediaName) => this.media[mediaName]);
}

/**
* Returns the current number of columns based on active media queries
* @type {number}
*/
get currentCols() {
return this._getAttributeForMedia(this.colsMedia, this.currentMedia) || 1;
}

@computed('gutter')
/**
* Returns the parsed responsive gutter sizes
* @type {Object<string,string|number>}
*/
get gutterMedia() {
return this._extractResponsiveSizes(this.gutter, rowHeightRegex);
return this._extractResponsiveSizes(this.args.gutter, rowHeightRegex);
}

@computed('gutterMedia', 'currentMedia.[]')
/**
* Returns the current gutter size based on active media queries
* @type {string}
*/
get currentGutter() {
return this._applyDefaultUnit(
this._getAttributeForMedia(this.gutterMedia, this.currentMedia) || 1
);
}

@computed('rowHeight')
/**
* Returns the parsed responsive row heights
* @type {Object<string,string|number>}
*/
get rowHeightMedia() {
let rowHeights = this._extractResponsiveSizes(
this.rowHeight,
this.args.rowHeight,
rowHeightRegex
);
if (Object.keys(rowHeights).length === 0) {
Expand All @@ -256,15 +352,29 @@ export default class PaperGridList extends Component.extend(ParentMixin) {
return rowHeights;
}

@computed('rowHeightMedia', 'currentMedia.[]')
/**
* Returns the calculated row height based on the current media query.
* @returns {string}
*/
get rowHeight() {
return this._getAttributeForMedia(this.rowHeightMedia, this.currentMedia);
}

/**
* Current row height mode ('fixed', 'ratio', or 'fit')
* @type {string}
*/
get currentRowMode() {
return this._getRowMode(this.rowHeight);
}

/**
* Returns the current row height based on the row mode.
* @type {string|number|undefined}
*/
get currentRowHeight() {
let rowHeight = this._getAttributeForMedia(
this.rowHeightMedia,
this.currentMedia
);
// eslint-disable-next-line ember/no-side-effects
this.set('currentRowMode', this._getRowMode(rowHeight));
switch (this._getRowMode(rowHeight)) {
let rowHeight = this.rowHeight;
switch (this.currentRowMode) {
case 'fixed': {
return this._applyDefaultUnit(rowHeight);
}
Expand Down
8 changes: 5 additions & 3 deletions addon/components/paper-grid-tile-footer.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<figcaption>
{{yield}}
</figcaption>
<md-grid-tile-footer ...attributes>
<figcaption>
{{yield}}
</figcaption>
</md-grid-tile-footer>
Loading
Loading