Skip to content

Commit

Permalink
use PropertyRequiredMixin in empty state and text input
Browse files Browse the repository at this point in the history
  • Loading branch information
dlockhart committed Nov 23, 2023
1 parent 0985fcd commit 6611e8b
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 142 deletions.
26 changes: 3 additions & 23 deletions components/empty-state/empty-state-action-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import '../button/button.js';
import '../button/button-subtle.js';
import { css, html, LitElement, nothing } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';

/**
* `d2l-empty-state-action-button` is an empty state action component that can be placed inside of the default slot of `empty-state-simple` or `empty-state-illustrated` to add a button action to the component.
* @fires d2l-empty-state-action - Dispatched when the action button is clicked
* @fires d2l-empty-state-illustrated-check - Internal event
*/
class EmptyStateActionButton extends LitElement {
class EmptyStateActionButton extends PropertyRequiredMixin(LitElement) {

static get properties() {
return {
/**
* REQUIRED: The action text to be used in the button
* @type {string}
*/
text: { type: String },
text: { type: String, required: true },
/**
* This will change the action button to use a primary button instead of the default subtle button. The primary attribute is only valid when `d2l-empty-state-action-button` is placed within `d2l-empty-state-illustrated` components
* @type {boolean}
Expand All @@ -37,8 +38,6 @@ class EmptyStateActionButton extends LitElement {
constructor() {
super();
this._illustrated = false;
this._missingTextErrorHasBeenThrown = false;
this._validatingTextTimeout = null;
}

connectedCallback() {
Expand All @@ -53,11 +52,6 @@ class EmptyStateActionButton extends LitElement {
});
}

firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._validateText();
}

render() {
let actionButton = nothing;
if (this.text) {
Expand All @@ -84,20 +78,6 @@ class EmptyStateActionButton extends LitElement {
this.dispatchEvent(new CustomEvent('d2l-empty-state-action'));
}

_validateText() {
clearTimeout(this._validatingTextTimeout);
// don't error immediately in case it doesn't get set immediately
this._validatingTextTimeout = setTimeout(() => {
this._validatingTextTimeout = null;
const hasText = (typeof this.text === 'string') && this.text.length > 0;

if (!hasText && !this._missingTextErrorHasBeenThrown) {
this._missingTextErrorHasBeenThrown = true;
setTimeout(() => { throw new Error('<d2l-empty-state-action-button>: missing required "text" attribute.'); });
}
}, 3000);
}

}

customElements.define('d2l-empty-state-action-button', EmptyStateActionButton);
40 changes: 4 additions & 36 deletions components/empty-state/empty-state-action-link.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
import { html, LitElement, nothing } from 'lit';
import { bodyCompactStyles } from '../typography/styles.js';
import { linkStyles } from '../link/link.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';

/**
* `d2l-empty-state-action-link` is an empty state action component that can be placed inside of the default slot of `empty-state-simple` or `empty-state-illustrated` to add a link action to the component.
*/
class EmptyStateActionLink extends LitElement {
class EmptyStateActionLink extends PropertyRequiredMixin(LitElement) {

static get properties() {
return {
/**
* REQUIRED: The action text to be used in the subtle button
* @type {string}
*/
text: { type: String },
text: { type: String, required: true },
/**
* REQUIRED: The action URL or URL fragment of the link
* @type {string}
*/
href: { type: String },
href: { type: String, required: true },
};
}

static get styles() {
return [bodyCompactStyles, linkStyles];
}

constructor() {
super();
this._missingHrefErrorHasBeenThrown = false;
this._missingTextErrorHasBeenThrown = false;
this._validatingAttributesTimeout = null;
}

firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._validateAttributes();
}

render() {
const actionLink = this.text && this.href
? html`
Expand All @@ -47,27 +36,6 @@ class EmptyStateActionLink extends LitElement {
return html`${actionLink}`;
}

_validateAttributes() {
clearTimeout(this._validatingAttributesTimeout);
// don't error immediately in case it doesn't get set immediately
this._validatingAttributesTimeout = setTimeout(() => {
this._validatingAttributesTimeout = null;

const hasHref = (typeof this.href === 'string') && this.href.length > 0;
const hasText = (typeof this.text === 'string') && this.text.length > 0;

if (!hasHref && !this._missingHrefErrorHasBeenThrown) {
this._missingHrefErrorHasBeenThrown = true;
setTimeout(() => { throw new Error('<d2l-empty-state-action-link>: missing required "href" attribute.'); });
}

if (!hasText && !this._missingTextErrorHasBeenThrown) {
this._missingTextErrorHasBeenThrown = true;
setTimeout(() => { throw new Error('<d2l-empty-state-action-link>: missing required "text" attribute.'); });
}
}, 3000);
}

}

customElements.define('d2l-empty-state-action-link', EmptyStateActionLink);
39 changes: 4 additions & 35 deletions components/empty-state/empty-state-illustrated.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { html, LitElement, nothing } from 'lit';
import { bodyCompactStyles } from '../typography/styles.js';
import { classMap } from 'lit/directives/class-map.js';
import { loadSvg } from '../../generated/empty-state/presetIllustrationLoader.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
import { runAsync } from '../../directives/run-async/run-async.js';
import { styleMap } from 'lit/directives/style-map.js';
Expand All @@ -15,15 +16,15 @@ const illustrationAspectRatio = 500 / 330;
* @slot - Slot for empty state actions
* @slot illustration - Slot for custom SVG content if `illustration-name` property is not set
*/
class EmptyStateIllustrated extends LitElement {
class EmptyStateIllustrated extends PropertyRequiredMixin(LitElement) {

static get properties() {
return {
/**
* REQUIRED: A description giving details about the empty state
* @type {string}
*/
description: { type: String },
description: { type: String, required: true },
/**
* The name of the preset image you would like to display in the component
* @type {string}
Expand All @@ -33,7 +34,7 @@ class EmptyStateIllustrated extends LitElement {
* REQUIRED: A title for the empty state
* @type {string}
*/
titleText: { type: String, attribute: 'title-text' },
titleText: { type: String, attribute: 'title-text', required: true },
_contentHeight: { state: true },
_titleSmall: { state: true }
};
Expand All @@ -46,11 +47,8 @@ class EmptyStateIllustrated extends LitElement {
constructor() {
super();
this._contentHeight = 330;
this._missingDescriptionErrorHasBeenThrown = false;
this._missingTitleTextErrorHasBeenThrown = false;
this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
this._titleSmall = false;
this._validatingAttributesTimeout = null;
}

connectedCallback() {
Expand All @@ -65,11 +63,6 @@ class EmptyStateIllustrated extends LitElement {
this._resizeObserver.disconnect();
}

firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._validateAttributes();
}

render() {
const illustrationContainerStyle = this._getIllustrationContainerStyle();
const titleClass = this._getTitleClass();
Expand Down Expand Up @@ -126,30 +119,6 @@ class EmptyStateIllustrated extends LitElement {
});
}

_validateAttributes() {
clearTimeout(this._validatingAttributesTimeout);
// don't error immediately in case it doesn't get set immediately
this._validatingAttributesTimeout = setTimeout(() => {
this._validatingAttributesTimeout = null;
const hasTitleText = (typeof this.titleText === 'string') && this.titleText.length > 0;
const hasDescription = (typeof this.description === 'string') && this.description.length > 0;

if (!hasTitleText && !this._missingTitleTextErrorHasBeenThrown) {
this._missingTitleTextErrorHasBeenThrown = true;
setTimeout(() => {
throw new Error('<d2l-empty-state-illustrated>: missing required "titleText" attribute.');
});
}

if (!hasDescription && !this._missingDescriptionErrorHasBeenThrown) {
this._missingDescriptionErrorHasBeenThrown = true;
setTimeout(() => {
throw new Error('<d2l-empty-state-illustrated>: missing required "description" attribute.');
});
}
}, 3000);
}

}

customElements.define('d2l-empty-state-illustrated', EmptyStateIllustrated);
30 changes: 3 additions & 27 deletions components/empty-state/empty-state-simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,36 @@ import '../button/button-subtle.js';
import { emptyStateSimpleStyles, emptyStateStyles } from './empty-state-styles.js';
import { html, LitElement } from 'lit';
import { bodyCompactStyles } from '../typography/styles.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';

/**
* The `d2l-empty-state-simple` component is an empty state component that displays a description. An empty state action component can be placed inside of the default slot to add an optional action.
* @slot - Slot for empty state actions
*/
class EmptyStateSimple extends RtlMixin(LitElement) {
class EmptyStateSimple extends PropertyRequiredMixin(RtlMixin(LitElement)) {

static get properties() {
return {
/**
* REQUIRED: A description giving details about the empty state
* @type {string}
*/
description: { type: String },
description: { type: String, required: true },
};
}

static get styles() {
return [bodyCompactStyles, emptyStateStyles, emptyStateSimpleStyles];
}

constructor() {
super();
this._missingDescriptionErrorHasBeenThrown = false;
this._validatingDescriptionTimeout = null;
}

firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._validateDescription();
}

render() {
return html`
<p class="d2l-body-compact d2l-empty-state-description">${this.description}</p>
<slot class="action-slot"></slot>
`;
}

_validateDescription() {
clearTimeout(this._validatingDescriptionTimeout);
// don't error immediately in case it doesn't get set immediately
this._validatingDescriptionTimeout = setTimeout(() => {
this._validatingDescriptionTimeout = null;
const hasDescription = (typeof this.description === 'string') && this.description.length > 0;

if (!hasDescription && !this._missingDescriptionErrorHasBeenThrown) {
this._missingDescriptionErrorHasBeenThrown = true;
setTimeout(() => { throw new Error('<d2l-empty-state-simple>: missing required "description" attribute.'); });
}
}, 3000);
}

}

customElements.define('d2l-empty-state-simple', EmptyStateSimple);
35 changes: 14 additions & 21 deletions components/inputs/input-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { inputStyles } from './input-styles.js';
import { LabelledMixin } from '../../mixins/labelled/labelled-mixin.js';
import { offscreenStyles } from '../offscreen/offscreen.js';
import { PerfMonitor } from '../../helpers/perfMonitor.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
import { styleMap } from 'lit/directives/style-map.js';
Expand All @@ -24,7 +25,7 @@ import { styleMap } from 'lit/directives/style-map.js';
* @fires change - Dispatched when an alteration to the value is committed (typically after focus is lost) by the user
* @fires input - Dispatched immediately after changes by the user
*/
class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement))))) {
class InputText extends PropertyRequiredMixin(FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement)))))) {

static get properties() {
return {
Expand Down Expand Up @@ -152,7 +153,18 @@ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(
* Accessible label for the unit which will not be visually rendered
* @type {string}
*/
unitLabel: { attribute: 'unit-label', type: String },
unitLabel: {
attribute: 'unit-label',
required: {
dependentProps: ['unit'],
message: (_value, elem) => `<d2l-input-text>: missing required attribute "unit-label" for unit "${elem.unit}"`,
validator: (_value, elem, hasValue) => {
const hasUnit = (typeof elem.unit === 'string') && elem.unit.length > 0;
return !(hasUnit && elem.unit !== '%' && !hasValue);
}
},
type: String
},
/**
* Value of the input
* @type {string}
Expand Down Expand Up @@ -270,15 +282,13 @@ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(
this._intersectionObserver = null;
this._isIntersecting = false;
this._lastSlotWidth = 0;
this._missingUnitLabelErrorHasBeenThrown = false;
this._prevValue = '';

this._handleBlur = this._handleBlur.bind(this);
this._handleFocus = this._handleFocus.bind(this);
this._handleMouseEnter = this._handleMouseEnter.bind(this);
this._handleMouseLeave = this._handleMouseLeave.bind(this);
this._perfMonitor = new PerfMonitor(this);
this._validatingUnitTimeout = null;
}

get value() { return this._value; }
Expand Down Expand Up @@ -355,7 +365,6 @@ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(
super.firstUpdated(changedProperties);

this._setValue(this.value, true);
this._validateUnit();

const container = this.shadowRoot && this.shadowRoot.querySelector('.d2l-input-text-container');
if (!container) return;
Expand Down Expand Up @@ -484,7 +493,6 @@ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(
changedProperties.forEach((oldVal, prop) => {
if (prop === 'unit' || prop === 'unitLabel') {
this._updateInputLayout();
this._validateUnit();
} else if (prop === 'validationError') {
if (oldVal && this.validationError) {
const tooltip = this.shadowRoot.querySelector('d2l-tooltip');
Expand Down Expand Up @@ -655,20 +663,5 @@ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(

}

_validateUnit() {
if (this._missingUnitLabelErrorHasBeenThrown) return;
clearTimeout(this._validatingUnitTimeout);
// don't error immediately in case it doesn't get set immediately
this._validatingUnitTimeout = setTimeout(() => {
this._validatingUnitTimeout = null;
const hasUnit = (typeof this.unit === 'string') && this.unit.length > 0;
const hasUnitLabel = (typeof this.unitLabel === 'string') && this.unitLabel.length > 0;
if (hasUnit && this.unit !== '%' && !hasUnitLabel) {
this._missingUnitLabelErrorHasBeenThrown = true;
throw new Error(`<d2l-input-text>: missing required attribute "unit-label" for unit "${this.unit}"`);
}
}, 3000);
}

}
customElements.define('d2l-input-text', InputText);

0 comments on commit 6611e8b

Please sign in to comment.