Skip to content

Commit

Permalink
feat(addon/components/paper-tab): converts to a glimmer component.
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhartstonge committed Dec 30, 2024
1 parent af9e992 commit cc007ed
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 52 deletions.
32 changes: 25 additions & 7 deletions addon/components/paper-tab.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
{{! template-lint-disable no-curly-component-invocation }}
{{#if (has-block)}}
{{yield}}
{{else}}
{{@name}}
{{/if}}
<PaperRipple/>
{{#let (element this.tag) as |Tag|}}
<Tag
class='md-tab
{{~if this.focused " md-focused"}}
{{~if this.isSelected " md-active"}}
{{@class}}'
aria-selected={{this.isSelected}}
disabled={{this.disabled}}
href={{this.maybeHref}}
style={{this.style}}
tabindex={{if this.disabled '-1' '0'}}
{{did-insert this.didInsertNode}}
{{did-update this.didUpdateNode @value}}
{{will-destroy this.willDestroyNode}}
{{on 'click' this.handleClick}}
...attributes
>
{{#if (has-block)}}
{{yield}}
{{else}}
{{@name}}
{{/if}}
<PaperRipple />
</Tag>
{{/let}}
212 changes: 167 additions & 45 deletions addon/components/paper-tab.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,194 @@
/* eslint-disable ember/classic-decorator-hooks, ember/classic-decorator-no-classic-methods, ember/no-classic-components, ember/no-computed-properties-in-native-classes, ember/no-mixins */
import {
classNames,
attributeBindings,
classNameBindings,
tagName,
} from '@ember-decorators/component';

import { computed } from '@ember/object';
import Component from '@ember/component';
/**
* @module ember-paper
*/
import Focusable from './-focusable';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { htmlSafe } from '@ember/template';
import { ChildMixin } from 'ember-composability-tools';
import FocusableMixin from 'ember-paper/mixins/focusable-mixin';
import { invokeAction } from 'ember-paper/utils/invoke-action';

@tagName('md-tab-item')
@classNames('md-tab')
@classNameBindings('isSelected:md-active')
@attributeBindings('isSelected:aria-selected', 'style', 'maybeHref:href')
export default class PaperTab extends Component.extend(
ChildMixin,
FocusableMixin
) {
// <a> tags have browser styles or are usually styled by the user
// this makes sure that tab item still looks good with an anchor tag
@computed('href')
import { assert } from '@ember/debug';

/**
* @class PaperTab
* @extends Focusable
*/
export default class PaperTab extends Focusable {
/**
* Reference to the component's DOM element.
*
* @type {HTMLElement}
*/
element;
/**
* The parent this component is bound to.
*
* @type {PaperTabs}
*/
parent;
/**
* Marks whether the component should register itself to the supplied parent.
*
* @type {Boolean}
*/
shouldRegister;
/**
* The top level tag to render. One of {'a', 'md-tab'}.
*
* @type {string}
* @private
* @default 'md-tab-item'
*/
tag;
/**
* provides a proxy value if one is not supplied by the user.
*
* @type {number|*}
* @private
*/
@tracked _value;
/**
* the number of pixels that the upper left corner of the current element is
* offset to the left within the {@link HTMLElement.offsetParent} node.
*
* @type{number}
*/
@tracked left;
/**
* the layout width of the element as an integer.
*
* @type{number}
*/
@tracked width;

/**
* @constructor
* @param owner
* @param args
*/
constructor(owner, args) {
super(owner, args);

this.tag = 'md-tab-item';
if (this.args.href) {
this.tag = 'a';
}

this.shouldRegister = this.args.shouldRegister || true;
if (this.shouldRegister) {
assert(
'A parent component should be supplied to <PaperTab>',
this.args.parentComponent
);
this.parent = this.args.parentComponent;
}
}

/**
* Performs any required DOM setup.
*
* @param {HTMLElement} element - the node that has been added to the DOM.
*/
@action didInsertNode(element) {
this.element = element;
this.left = element.offsetLeft;
this.width = element.offsetWidth;

this.registerListeners(element);

if (this.shouldRegister) {
this.parent.registerChild(this);
}
}

/**
* didUpdateNode is called when tracked component attributes change.
*/
@action didUpdateNode() {
if (this.args.value) {
this.value = this.args.value;
}
}

/**
* Performs any required DOM teardown.
*
* @param {HTMLElement} element - the node to be removed from the DOM.
*/
@action willDestroyNode(element) {
this.unregisterListeners(element);
}

/**
* lifecycle hook to perform non-DOM related teardown.
*/
willDestroy() {
super.willDestroy();

if (this.shouldRegister) {
this.parent.unregisterChild(this);
}
}

/**
* <a> tags have browser styles or are usually styled by the user
* this makes sure that tab item still looks good with an anchor tag.
*
* @returns {string|undefined}
*/
get style() {
if (this.href) {
if (this.args.href) {
return htmlSafe('text-decoration: none; border: none;');
} else {
return undefined;
}
}

@computed('href', 'disabled')
/**
* maybeHref returns the user supplied href link url.
*
* @returns {string|undefined}
*/
get maybeHref() {
if (this.href && !this.disabled) {
return this.href;
if (this.args.href && !this.disabled) {
return this.args.href;
} else {
return undefined;
}
}

@computed('selected', 'value')
/**
* computes whether this is the currently selected tab.
*
* @returns {boolean}
*/
get isSelected() {
return this.selected === this.value;
}

init() {
super.init(...arguments);
if (this.href) {
this.set('tagName', 'a');
}
return this.args.selected === this.value;
}

// this method is called by the parent
updateDimensions() {
// this is the true current width
// it is used to calculate the ink bar position & pagination offset
this.setProperties({
left: this.element.offsetLeft,
width: this.element.offsetWidth,
});
this.left = this.element.offsetLeft;
this.width = this.element.offsetWidth;
}

click() {
get value() {
// enable support for user supplied value
return this.args.value || this._value;
}
set value(value) {
this._value = value;
}

@action handleClick(e) {
if (!this.disabled) {
invokeAction(this, 'onClick', ...arguments);
invokeAction(this, 'onSelect', this);
if (this.args.onClick) {
this.args.onClick(e);
}

if (this.args.onSelect) {
this.args.onSelect(this);
}
}
}
}

0 comments on commit cc007ed

Please sign in to comment.