@telekom/scale-components
Version:
Scale is the digital design system for Telekom products and experiences.
681 lines (680 loc) • 19 kB
JavaScript
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Component, Prop, Event, h, Host, State, Element, } from '@stencil/core';
import classNames from 'classnames';
import { emitEvent, generateUniqueId } from '../../utils/utils';
import statusNote from '../../utils/status-note';
export class Dropdown {
constructor() {
/** (optional) Input name */
this.name = '';
/** (optional) Input label */
this.label = '';
/** (optional) Input helper text */
this.helperText = '';
/** @deprecated - invalid should replace status */
this.status = '';
/** (optional) Input status */
this.invalid = false;
/** (optional) Variant */
this.variant = 'informational';
/** (optional) Input value */
this.value = '';
/** (optional) Makes type `select` behave as a controlled component in React */
this.controlled = false;
/** (optional) to avoid displaying the label */
this.hideLabelVisually = false;
this.internalId = generateUniqueId();
// Handle change on <select> independently
// so we can allow "controlled" (React) behavior,
// in which only the `value` changing does update
// the actual <select> value, not the user's input.
this.handleSelectChange = (event) => {
const target = event.target;
if (this.controlled) {
emitEvent(this, 'scaleChange', { value: target.value });
this.selectElement.value = String(this.value);
this.forceUpdate = String(Date.now());
}
else {
this.value = target.value || '';
this.emitChange();
}
};
this.handleInput = (event) => {
const target = event.target;
if (target) {
this.value = target.value || '';
this.emitChange();
}
emitEvent(this, 'scaleInput', event);
};
this.handleChange = (event) => {
const target = event.target;
if (target) {
this.value = target.value || '';
this.emitChange();
}
};
this.handleFocus = () => {
emitEvent(this, 'scaleFocus');
};
this.handleBlur = () => {
emitEvent(this, 'scaleBlur');
};
this.handleKeyDown = (event) => {
emitEvent(this, 'scaleKeyDown', event);
};
}
componentWillLoad() {
this.hasSlotIcon = !!this.hostElement.querySelector('[slot="icon"]');
if (this.inputId == null) {
this.inputId = 'input-dropdown-' + this.internalId;
}
}
componentDidLoad() {
// Keep this.value up-to-date for type="select".
// This is important also for React, where `value` is used to control the element state.
const select = this.selectElement;
const selectedValue = select.selectedIndex > -1
? select.options[select.selectedIndex].value
: null;
// If we have a `value` passed, set it on the <select> element
// Otherwise, if we have an <option selected>, set its value on `value`
if (this.value) {
select.value = String(this.value);
}
else if (selectedValue) {
this.value = selectedValue;
}
// This is a workaroud to prevent a bug in Stencil:
// when using slots without Shadow DOM (possible only with Stencil)
// sometimes an update in the Light DOM does not trigger a re-render
// thus causing unexpected results.
// https://gitlab.com/scale-ds/scale-telekom/-/issues/16
if (this.selectElement) {
this.mutationObserver = new MutationObserver(() => {
this.forceUpdate = String(Date.now());
});
this.mutationObserver.observe(this.hostElement, {
childList: true,
subtree: true,
});
}
}
componentDidUpdate() {
this.hasSlotIcon = !!this.hostElement.querySelector('[slot="icon"]');
}
componentDidRender() {
// When type `select` and `controlled` is true,
// make sure the <select> is always in sync with the value.
const value = this.value == null ? '' : this.value.toString();
if (this.controlled && this.selectElement.value.toString() !== value) {
this.selectElement.value = value;
}
if (this.status !== '') {
statusNote({
tag: 'deprecated',
message: 'Property "status" is deprecated. Please use the "invalid" property!',
type: 'warn',
source: this.hostElement,
});
}
if (this.size) {
statusNote({
tag: 'deprecated',
message: 'Property "size" is deprecated. Please use css overwrite!',
type: 'warn',
source: this.hostElement,
});
}
}
disconnectedCallback() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
}
}
// We're not watching `value` like we used to
// because we get unwanted `scaleChange` events
// because how we keep this.value up-to-date for type="select"
// `this.value = selectedValue`
emitChange() {
emitEvent(this, 'scaleChange', {
value: this.value == null ? this.value : this.value.toString(),
});
}
render() {
const ariaInvalidAttr = this.status === 'error' || this.invalid ? { 'aria-invalid': true } : {};
const helperTextId = `helper-message-${this.internalId}`;
const ariaDescribedByAttr = { 'aria-describedBy': helperTextId };
return (h(Host, null,
h("div", { class: this.getCssClassMap() },
h("label", { class: "input__label", htmlFor: this.inputId }, this.label),
h("div", { class: "input__dropdown-wrapper" },
h("select", Object.assign({ ref: (el) => (this.selectElement = el), class: "input__dropdown",
// @ts-ignore
value: this.value, onChange: this.handleSelectChange, onFocus: this.handleFocus, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, disabled: this.disabled, required: this.required, multiple: this.multiple, id: this.inputId, name: this.name, size: this.visibleSize }, ariaInvalidAttr, (this.helperText ? ariaDescribedByAttr : {})),
h("slot", null)),
h("div", { class: "input__dropdown-icon" }, this.hasSlotIcon ? (h("slot", { name: "icon" })) : (h("scale-icon-navigation-collapse-down", { size: 20, decorative: true })))),
this.helperText && (h("scale-helper-text", { helperText: this.helperText, variant: this.invalid ? 'danger' : this.variant })))));
}
getCssClassMap() {
return classNames('dropdown', this.disabled && `dropdown--disabled`, this.transparent && 'dropdown--transparent', this.status && `dropdown--status-${this.status}`, this.helperText && 'dropdown--helper-text', this.variant &&
`dropdown--variant-${this.invalid ? 'danger' : this.variant}`, this.value != null && this.value !== '' && 'animated', this.hideLabelVisually && 'dropdown--hide-label');
}
static get is() { return "scale-dropdown"; }
static get originalStyleUrls() { return {
"$": ["./dropdown.css"]
}; }
static get styleUrls() { return {
"$": ["dropdown.css"]
}; }
static get properties() { return {
"name": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input name"
},
"attribute": "name",
"reflect": false,
"defaultValue": "''"
},
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "(optional) Input label"
},
"attribute": "label",
"reflect": false,
"defaultValue": "''"
},
"helperText": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input helper text"
},
"attribute": "helper-text",
"reflect": false,
"defaultValue": "''"
},
"status": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "- invalid should replace status"
}],
"text": ""
},
"attribute": "status",
"reflect": false,
"defaultValue": "''"
},
"size": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": undefined
}],
"text": ""
},
"attribute": "size",
"reflect": false
},
"invalid": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input status"
},
"attribute": "invalid",
"reflect": false,
"defaultValue": "false"
},
"variant": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'informational' | 'warning' | 'danger' | 'success'",
"resolved": "\"danger\" | \"informational\" | \"success\" | \"warning\"",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Variant"
},
"attribute": "variant",
"reflect": false,
"defaultValue": "'informational'"
},
"disabled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": ""
},
"attribute": "disabled",
"reflect": false
},
"required": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input required"
},
"attribute": "required",
"reflect": false
},
"value": {
"type": "any",
"mutable": true,
"complexType": {
"original": "string | number | null",
"resolved": "number | string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input value"
},
"attribute": "value",
"reflect": false,
"defaultValue": "''"
},
"inputId": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Input checkbox id"
},
"attribute": "input-id",
"reflect": false
},
"multiple": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) select multiple options"
},
"attribute": "multiple",
"reflect": false
},
"visibleSize": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) the number of visible options in a select drop-down list"
},
"attribute": "visible-size",
"reflect": false
},
"transparent": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) input background transparent"
},
"attribute": "transparent",
"reflect": false
},
"controlled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Makes type `select` behave as a controlled component in React"
},
"attribute": "controlled",
"reflect": false,
"defaultValue": "false"
},
"hideLabelVisually": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) to avoid displaying the label"
},
"attribute": "hide-label-visually",
"reflect": false,
"defaultValue": "false"
},
"styles": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Injected CSS styles"
},
"attribute": "styles",
"reflect": false
}
}; }
static get states() { return {
"forceUpdate": {}
}; }
static get events() { return [{
"method": "scaleInput",
"name": "scale-input",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a keyboard input occurred."
},
"complexType": {
"original": "KeyboardEvent",
"resolved": "KeyboardEvent",
"references": {
"KeyboardEvent": {
"location": "global"
}
}
}
}, {
"method": "scaleInputLegacy",
"name": "scaleInput",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "in v3 in favor of kebab-case event names"
}],
"text": ""
},
"complexType": {
"original": "KeyboardEvent",
"resolved": "KeyboardEvent",
"references": {
"KeyboardEvent": {
"location": "global"
}
}
}
}, {
"method": "scaleChange",
"name": "scale-change",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the value has changed."
},
"complexType": {
"original": "InputChangeEventDetail",
"resolved": "InputChangeEventDetail",
"references": {
"InputChangeEventDetail": {
"location": "global"
}
}
}
}, {
"method": "scaleChangeLegacy",
"name": "scaleChange",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "in v3 in favor of kebab-case event names"
}],
"text": ""
},
"complexType": {
"original": "InputChangeEventDetail",
"resolved": "InputChangeEventDetail",
"references": {
"InputChangeEventDetail": {
"location": "global"
}
}
}
}, {
"method": "scaleFocus",
"name": "scale-focus",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the input has focus."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "scaleFocusLegacy",
"name": "scaleFocus",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "in v3 in favor of kebab-case event names"
}],
"text": ""
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "scaleBlur",
"name": "scale-blur",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the input loses focus."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "scaleBlurLegacy",
"name": "scaleBlur",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "in v3 in favor of kebab-case event names"
}],
"text": ""
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "scaleKeyDown",
"name": "scale-keydown",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted on keydown."
},
"complexType": {
"original": "KeyboardEvent",
"resolved": "KeyboardEvent",
"references": {
"KeyboardEvent": {
"location": "global"
}
}
}
}, {
"method": "scaleKeyDownLegacy",
"name": "scaleKeydown",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "in v3 in favor of kebab-case event names"
}],
"text": ""
},
"complexType": {
"original": "KeyboardEvent",
"resolved": "KeyboardEvent",
"references": {
"KeyboardEvent": {
"location": "global"
}
}
}
}]; }
static get elementRef() { return "hostElement"; }
}