Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@anypoint-web-components/anypoint-dropdown-menu

Package Overview
Dependencies
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@anypoint-web-components/anypoint-dropdown-menu - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

LICENSE.md

572

AnypointDropdownMenu.js

@@ -26,38 +26,56 @@ import { html, css, LitElement } from 'lit-element';

text-align: left;
cursor: default;
border: 1px var(--anypoint-dropdown-menu-border-color, #E0E0E0) solid;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-tap-highlight-color: transparent;
background-color: var(--anypoint-dropdown-menu-background-color, #F5F5F5);
border-radius: 3px;
outline: none;
height: 56px;
box-sizing: border-box;
font-size: 1rem;
/* Anypoint UI controls margin in forms */
margin: 12px 8px;
outline: none;
}
:host([disabled]) anypoint-icon-button {
:host([disabled]) .trigger-icon {
pointer-events: none;
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.33);
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.43);
}
:host([disabled]) .input {
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.33);
border-bottom: 1px dashed var(--paper-dropdown-menu-color, var(--secondary-text-color));
:host([disabled]) .label.resting {
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.43);
}
:host([disabled]) .label.without-value {
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.33);
:host([nolabelfloat]) {
height: 40px;
}
:host([invalid]) .input,
:host(:invalid) .input {
border-bottom: 2px solid var(--anypoint-dropdown-error-color, var(--error-color));
.input-container {
position: relative;
height: 100%;
width: inherit;
background-color: var(--anypoint-dropdown-menu-background-color, #F5F5F5);
border: 1px var(--anypoint-dropdown-menu-border-color, transparent) solid;
border-radius: 4px 4px 0 0;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--anypoint-dropdown-menu-border-bottom-color, #8e8e8e);
transition: border-bottom-color 0.22s linear;
transform-origin: center center;
cursor: default;
}
:host([opened]),
:host([focused]),
:host(:focus) {
background-color: var(--anypoint-dropdown-menu-focus-background-color, #fff);
border-color: var(--anypoint-dropdown-menu-hover-border-color, var(--anypoint-color-coreBlue3));
:host([invalid]) .input-container,
:host(:invalid) .input-container {
border-bottom: 1px solid var(--anypoint-dropdown-error-color, var(--error-color)) !important;
}
:host([disabled]) .input-container {
opacity: var(--anypoint-dropdown-menu-disabled-opacity, 0.43);
border-bottom: 1px dashed var(--paper-dropdown-menu-color, var(--secondary-text-color));
}
:host([opened]) .input-container,
:host([focused]) .input-container,
:host(:focus) .input-container {
border-bottom-color: var(--anypoint-dropdown-menu-hover-border-color, var(--anypoint-color-coreBlue3));
}
.input-wrapper {

@@ -67,2 +85,4 @@ display: flex;

align-items: center;
height: 100%;
position: relative;
}

@@ -72,7 +92,7 @@

flex: 1;
margin: 0px 0px 0px 8px;
margin: 12px 0px 0px 8px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: calc(100% - 40px);
overflow: auto;
}

@@ -87,5 +107,9 @@

text-align: right;
margin: 0px 8px 0px 0px;
margin: 12px 8px 0px 0px;
}
:host([nolabelfloat]) .input {
margin-top: 0 !important;
}
.input-spacer {

@@ -96,19 +120,19 @@ visibility: hidden;

anypoint-dropdown {
width: inherit;
}
.label {
position: absolute;
font-size: 13px;
transition: top 0.12s ease-in-out;
will-change: top;
transition: transform 0.12s ease-in-out, max-width 0.12s ease-in-out;
will-change: transform;
border-radius: 3px;
margin: 0;
padding: 0;
left: 8px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: calc(100% - 40px);
z-index: 1;
max-width: calc(100% - 16px);
text-overflow: clip;
color: var(--anypoint-dropdown-menu-label-color, #616161);
transform-origin: left top;
left: 8px;
top: calc(100% / 2 - 8px);
}

@@ -127,14 +151,23 @@

left: auto;
transform-origin: right top;
}
.label.without-value {
top: calc(100% / 2 - 8px);
font-size: 14px;
.label.resting {
transform: translateY(0) scale(1);
}
.label.with-value {
background-color: var(--anypoint-dropdown-menu-label-background-color, white);
top: -8px;
.label.floating {
transform: translateY(-80%) scale(0.75);
max-width: calc(100% + 20%);
}
:host([nolabelfloat]:not([legacy])) .label.floating {
display: none !important;
}
:host([invalid]) .label,
:host(:invalid) .label {
color: var(--anypoint-dropdown-error-color, var(--error-color)) !important;
}
.trigger-icon {

@@ -144,2 +177,3 @@ transform: rotate(0);

will-change: transform;
color: var(--anypoint-dropdown-menu-label-color, #616161);
}

@@ -151,14 +185,202 @@

:host([opened]) .trigger-icon,
:host([focused]) .trigger-icon,
:host(:focus) .trigger-icon {
color: var(--anypoint-dropdown-menu-trigger-icon-active-color, var(--primary-color));
}
anypoint-dropdown {
border-bottom: 2px var(--anypoint-dropdown-menu-border-color, #E0E0E0) solid;
border-top: 2px var(--anypoint-dropdown-menu-border-color, #E0E0E0) solid;
margin-top: 41px;
margin-top: 58px;
width: inherit;
}
:host([verticalalign="bottom"]) anypoint-dropdown {
margin-bottom: 41px;
margin-bottom: 58px;
margin-top: auto;
}
:host([nolabelfloat]) anypoint-dropdown {
margin-top: 40px;
}
.assistive-info {
overflow: hidden;
}
.invalid,
.info {
padding: 0;
margin: 0 0 0 8px;
font-size: .875rem;
transition: transform 0.12s ease-in-out;
}
.info {
color: var(--anypoint-dropdown-menu-info-message-color, #616161);
}
.info.hidden {
transform: translateY(-200%);
}
.invalid {
color: var(--anypoint-dropdown-menu-error-color, var(--error-color));
}
.invalid.hidden,
.invalid.info-offset.hidden {
transform: translateY(-200%);
}
.invalid.info-offset {
transform: translateY(-12px);
}
/* Outlined theme */
:host([outlined]) .input-container {
border: 1px var(--anypoint-dropdown-menu-border-color, #8e8e8e) solid;
background-color: var(--anypoint-dropdown-menu-background-color, #fff);
border-radius: 4px;
transition: border-bottom-color 0.22s linear;
}
:host([outlined]) .input {
margin-top: 0;
}
:host([outlined]) .label.resting {
margin-top: 0;
top: calc(100% / 2 - 8px);
}
:host([outlined]) .label.floating {
background-color: var(--anypoint-dropdown-menu-label-background-color, white);
transform: translateY(-130%) scale(0.75);
max-width: 120%;
padding: 0 2px;
}
/* Anypoint legacy theme */
:host([legacy]) {
height: 40px;
margin-top: 20px;
}
:host([legacy]) .input-container {
border: none;
border-left: 2px var(--anypoint-dropdown-menu-border-color, #8e8e8e) solid;
border-right: 2px var(--anypoint-dropdown-menu-border-color, #8e8e8e) solid;
border-radius: 0;
box-sizing: border-box;
}
:host([legacy][focused]) .input-container,
:host([legacy]:hover) .input-container {
border-left-color: var(--anypoint-dropdown-menu-legacy-focus-border-color, #58595a);
border-right-color: var(--anypoint-dropdown-menu-legacy-focus-border-color, #58595a);
background-color: var(--anypoint-dropdown-menu-legacy-focus-background-color, #f9fafb);
}
:host([legacy][invalid]) .input-container {
border-left-color: var(--anypoint-dropdown-menu-error-color, var(--error-color));
border-right-color: var(--anypoint-dropdown-menu-error-color, var(--error-color));
}
:host([legacy]) .label {
font-size: .875rem;
left: 0;
top: -18px;
transform: none;
font-weight: 500;
color: var(--anypoint-dropdown-menu-legacy-label-color, #616161);
}
:host([legacy]) anypoint-dropdown {
margin-top: 40px;
}
:host([legacy]) .input {
margin-top: 0;
}
`;
}
render() {
const {
opened,
horizontalAlign,
verticalAlign,
dynamicAlign,
horizontalOffset,
verticalOffset,
noOverlap,
openAnimationConfig,
closeAnimationConfig,
noAnimations,
allowOutsideScroll,
restoreFocusOnClose,
value,
invalidMessage,
infoMessage,
legacy,
_labelClass,
_errorAddonClass,
_infoAddonClass
} = this;
const renderValue = value || '';
return html`
<div class="input-container">
<div class="${_labelClass}">
<slot name="label"></slot>
</div>
<div class="input-wrapper">
<div class="input">
${renderValue}
<span class="input-spacer">&nbsp;</span>
</div>
<anypoint-icon-button @click="${this.toggle}" aria-label="Toggles dropdown menu">
<button tabindex="-1" aria-label="Toggles dropdown menu">
<iron-icon
class="trigger-icon ${opened ? 'opened' : ''}"
icon="paper-dropdown-menu:arrow-drop-down"></iron-icon>
</button>
</anypoint-icon-button>
</div>
<anypoint-dropdown
.opened="${opened}"
.horizontalAlign="${horizontalAlign}"
.verticalAlign="${verticalAlign}"
.dynamicAlign="${dynamicAlign}"
.horizontalOffset="${horizontalOffset}"
.verticalOffset="${verticalOffset}"
.noOverlap="${noOverlap}"
.openAnimationConfig="${openAnimationConfig}"
.closeAnimationConfig="${closeAnimationConfig}"
.noAnimations="${noAnimations}"
.allowOutsideScroll="${allowOutsideScroll}"
.restoreFocusOnClose="${restoreFocusOnClose}"
?legacy="${legacy}"
@overlay-closed="${this._dropdownClosed}"
@overlay-opened="${this._dropdownOpened}"
@select="${this._selectHandler}"
@deselect="${this._deselectHandler}">
<div slot="dropdown-content" class="dropdown-content">
<slot id="content" name="dropdown-content"></slot>
</div>
</anypoint-dropdown>
</div>
<div class="assistive-info">
${infoMessage ? html`<p class="${_infoAddonClass}">${infoMessage}</p>` : undefined}
${invalidMessage ?
html`<p class="${_errorAddonClass}">${invalidMessage}</p>` :
undefined}
</div>
`;
}
/**

@@ -179,2 +401,112 @@ * For form-associated custom elements. Marks this custom element

get validationStates() {
return this._validationStates;
}
set validationStates(value) {
const old = this._validationStates;
/* istanbul ignore if */
if (old === value) {
return;
}
this._validationStates = value;
/* istanbul ignore else */
if (this.requestUpdate) {
this.requestUpdate('validationStates', old);
}
this._hasValidationMessage = !!(value && value.length);
this._validationStatesChanged(value);
this.dispatchEvent(new CustomEvent('validationstates-changed', {
detail: {
value
}
}));
}
get hasValidationMessage() {
return this._hasValidationMessage;
}
get _hasValidationMessage() {
return this.__hasValidationMessage;
}
set _hasValidationMessage(value) {
const old = this.__hasValidationMessage;
/* istanbul ignore if */
if (old === value) {
return;
}
this.__hasValidationMessage = value;
/* istanbul ignore else */
if (this.requestUpdate) {
this.requestUpdate('hasValidationMessage', old);
}
this.__hasValidationMessage = value;
this.dispatchEvent(new CustomEvent('hasvalidationmessage-changed', {
detail: {
value
}
}));
}
get autoValidate() {
return this._autoValidate;
}
set autoValidate(value) {
const old = this._autoValidate;
/* istanbul ignore if */
if (old === value) {
return;
}
this._autoValidate = value;
this._autoValidateChanged(value);
}
get invalidMessage() {
return this._invalidMessage;
}
set invalidMessage(value) {
const old = this._invalidMessage;
/* istanbul ignore if */
if (old === value) {
return;
}
this._invalidMessage = value;
/* istanbul ignore else */
if (this.requestUpdate) {
this.requestUpdate('invalidMessage', old);
}
this._hasValidationMessage = this.invalid && !!value;
}
get _labelClass() {
const labelFloating = !!this.value;
let klas = 'label';
klas += labelFloating ? ' floating' : ' resting';
return klas;
}
get _infoAddonClass() {
let klas = 'info';
const isInavlidWithMessage = !!this.invalidMessage && this.invalid;
if (isInavlidWithMessage) {
klas += ' hidden';
}
return klas;
}
get _errorAddonClass() {
let klas = 'invalid';
if (!this.invalid) {
klas += ' hidden';
}
if (this.infoMessage) {
klas += ' info-offset';
}
return klas;
}
static get properties() {

@@ -285,6 +617,44 @@ return {

/**
* The error message to display when the input is invalid.
*/
invalidMessage: { type: String },
/**
* Assistive text value.
* Rendered beflow the input.
*/
infoMessage: { type: String },
/**
* After calling `validate()` this will be populated by latest result of the test for each
* validator. Result item will contain following properties:
*
* - validator {String} Name of the validator
* - valid {Boolean} Result of the test
* - message {String} Error message, populated only if `valid` equal `false`
*
* This property is `undefined` if `validator` is not set.
*/
validationStates: { type: Array },
/**
* Value computed from `invalidMessage`, `invalid` and `validationStates`.
* True if the validation message should be displayed.
*/
_hasValidationMessage: { type: Boolean },
/**
* Will position the list around the button without overlapping
* it.
*/
noOverlap: { type: Boolean }
noOverlap: { type: Boolean },
/**
* Enables outlined theme.
*/
outlined: { type: Boolean, reflect: true },
/**
* Enables Anypoint legacy theme.
*/
legacy: { type: Boolean, reflect: true },
/**
* When set the label is rendered only when not selected state.
* It is useful when using the dropdown in an application menu bar.
*/
noLabelFloat: { type: Boolean, reflect: true }
};

@@ -370,2 +740,3 @@ }

this.restoreFocusOnClose = false;
this.value = '';

@@ -634,63 +1005,60 @@ this._clickHandler = this._clickHandler.bind(this);

}
/**
* Called when validation states changed.
* Validation states are set by validatable mixin and is a result of calling
* a custom validator. Each validator returns an object with `valid` and `message`
* properties.
*
* See `ValidatableMixin` for more information.
*
* @param {?Array<Object>} states
*/
_validationStatesChanged(states) {
if (!states || !states.length) {
return;
}
const parts = [];
for (let i = 0, len = states.length; i < len; i++) {
if (!states[i].valid) {
parts[parts.length] = states[i].message;
}
}
this.invalidMessage = parts.join('. ');
}
/**
* Calles when `autoValidate` changed
* @param {Boolean} value
*/
_autoValidateChanged(value) {
if (value) {
this.validate();
}
}
/**
* From `ValidatableMixin`
* @param {Boolean} value Current invalid sate
*/
_invalidChanged(value) {
super._invalidChanged(value);
this._hasValidationMessage = value && !!this.invalidMessage;
this._ensureInvalidAlertSate(value);
}
render() {
const {
opened,
horizontalAlign,
verticalAlign,
dynamicAlign,
horizontalOffset,
verticalOffset,
noOverlap,
openAnimationConfig,
closeAnimationConfig,
noAnimations,
allowOutsideScroll,
restoreFocusOnClose,
value
} = this;
const renderValue = opened ? '' : value || '';
return html`
<div class="label ${value && !opened ? 'with-value' : 'without-value'}">
<slot name="label"></slot>
</div>
<div class="input-wrapper">
<div class="input">
${renderValue}
<span class="input-spacer">&nbsp;</span>
</div>
<anypoint-icon-button @click="${this.toggle}" aria-label="Toggles dropdown menu">
<button tabindex="-1" aria-label="Toggles dropdown menu">
<iron-icon
class="trigger-icon ${opened ? 'opened' : ''}"
icon="paper-dropdown-menu:arrow-drop-down"></iron-icon>
</button>
</anypoint-icon-button>
</div>
<anypoint-dropdown
.opened="${opened}"
.horizontalAlign="${horizontalAlign}"
.verticalAlign="${verticalAlign}"
.dynamicAlign="${dynamicAlign}"
.horizontalOffset="${horizontalOffset}"
.verticalOffset="${verticalOffset}"
.noOverlap="${noOverlap}"
.openAnimationConfig="${openAnimationConfig}"
.closeAnimationConfig="${closeAnimationConfig}"
.noAnimations="${noAnimations}"
.allowOutsideScroll="${allowOutsideScroll}"
.restoreFocusOnClose="${restoreFocusOnClose}"
@overlay-closed="${this._dropdownClosed}"
@overlay-opened="${this._dropdownOpened}"
@select="${this._selectHandler}"
@deselect="${this._deselectHandler}">
<div slot="dropdown-content" class="dropdown-content">
<slot id="content" name="dropdown-content"></slot>
</div>
</anypoint-dropdown>
`;
_ensureInvalidAlertSate(invalid) {
if (!this.invalidMessage) {
return;
}
const node = this.shadowRoot.querySelector('p.invalid');
if (!node) {
return;
}
if (invalid) {
node.setAttribute('role', 'alert');
} else {
node.removeAttribute('role');
}
setTimeout(() => {
node.removeAttribute('role');
}, 1000);
}
}

14

package.json
{
"name": "@anypoint-web-components/anypoint-dropdown-menu",
"description": "Accessible dropdown menu for Anypoint platform",
"version": "0.1.0",
"version": "0.1.1",
"license": "Apache-2.0",

@@ -40,3 +40,3 @@ "main": "index.js",

"devDependencies": {
"@advanced-rest-client/arc-demo-helper": "^1.0.9",
"@advanced-rest-client/arc-demo-helper": "^1.0.10",
"@advanced-rest-client/eslint-config": "^1.0.6",

@@ -46,9 +46,11 @@ "@advanced-rest-client/prettier-config": "^0.1.0",

"@anypoint-web-components/anypoint-button": "^1.0.1",
"@anypoint-web-components/anypoint-checkbox": "^1.0.0",
"@anypoint-web-components/anypoint-item": "^1.0.1",
"@anypoint-web-components/anypoint-listbox": "^1.0.1",
"@anypoint-web-components/anypoint-radio-button": "^0.1.1",
"@anypoint-web-components/anypoint-styles": "^1.0.0-preview.1",
"@commitlint/cli": "^8.1.0",
"@commitlint/config-conventional": "^7.0.0",
"@open-wc/testing": "^2.2.1",
"@open-wc/testing-karma": "^3.1.5",
"@open-wc/testing": "^2.2.8",
"@open-wc/testing-karma": "^3.1.13",
"@polymer/gen-typescript-declarations": "^1.6.2",

@@ -58,7 +60,7 @@ "@polymer/iron-image": "^3.0.2",

"deepmerge": "^4.0.0",
"es-dev-server": "^1.8.3",
"es-dev-server": "^1.10.4",
"husky": "^1.0.0",
"karma": "^4.2.0",
"lint-staged": "^9.2.1",
"sinon": "^7.3.2",
"sinon": "^7.4.1",
"web-animations-js": "^2.3.2"

@@ -65,0 +67,0 @@ },

@@ -7,8 +7,30 @@ [![Published on NPM](https://img.shields.io/npm/v/@anypoint-web-components/anypoint-dropdown-menu.svg)](https://www.npmjs.com/package/@anypoint-web-components/anypoint-dropdown-menu)

A form element to select value from the list of options, styled for Anypoint platform.
This component is based on Material Design menu and adjusted for Anypoint platform components.
## Accessibility
Anypoint web components are set of components that allows to build Anypoint enabled UI in open source projects.
The element works perfectly with `anypoint-listbox` which together creates an accessible list of options. The listbox can be replaced by any other element that support similar functionality but make sure it has an appropriate aria support.
Exposed dropdown menus display the currently selected menu item above the menu.
They can be used only when a single menu item can be chosen at a time.
## Styling options
The element has three built-in themes:
- Material Design - Filled
- Material Design - Outlined
- Anypoint Design - Legacy
By default the input renders `filled` dropdown list.
![Filled menu](demo/filled.png)
Outlined style is rendered when `outlined` property is set.
![Outlined menu](demo/outlined.png)
Anypoint ready styles are rendered when `legacy` property is set.
![Legacy menu](demo/legacy.png)
OSS application should not use Anypoint based styling as it's protected by MuleSoft copyrights. This property is reserved for OSS applications embedded in the Anypoint platform.
## Usage

@@ -75,2 +97,43 @@

### Assistive text
Assistive text allows the user to better understand what kind of input is required. It can be an info message or invalid message when invalid
input has been detected.
#### Info message
Info message provides the user with additional description for the field. It should be used when the label can be confusing or to ensure the user about the reason of collecting the input.
```html
<anypoint-dropdown-menu infomessage="Will be added to your order.">
<label slot="label">Select a dinosaur</label>
<anypoint-listbox slot="dropdown-content" tabindex="-1">
<anypoint-item>item 1</anypoint-item>
<anypoint-item>item 2</anypoint-item>
<anypoint-item>item 3</anypoint-item>
</anypoint-listbox>
</anypoint-dropdown-menu>
```
![Info message](demo/info-message.png)
Do not try to put too detailed information. The user should be able to scan the message in a fraction of a second. Treat it as an additional text for the label.
#### Invalid message
Error message should help the user recover from the error state. Use clear message with simple instructions of how to fix the problem, for example `Only letters are allowed`.
```html
<anypoint-dropdown-menu invalidmessage="This value is required" invalid required>
<label slot="label">Select a dinosaur</label>
<anypoint-listbox slot="dropdown-content" tabindex="-1">
<anypoint-item>item 1</anypoint-item>
<anypoint-item>item 2</anypoint-item>
<anypoint-item>item 3</anypoint-item>
</anypoint-listbox>
</anypoint-dropdown-menu>
```
![Invalid message](demo/invalid-message.png)
### Form-associated custom elements

@@ -101,2 +164,6 @@

## Accessibility
The element works perfectly with `anypoint-listbox` which together creates an accessible list of options. The listbox can be replaced by any other element that support similar functionality but make sure it has an appropriate aria support.
## Development

@@ -103,0 +170,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc