@spectrum-web-components/menu
Advanced tools
Comparing version 0.8.11-alpha.1 to 0.9.0
{ | ||
"name": "@spectrum-web-components/menu", | ||
"version": "0.8.11-alpha.1+204701b5", | ||
"version": "0.9.0", | ||
"publishConfig": { | ||
@@ -53,7 +53,7 @@ "access": "public" | ||
"dependencies": { | ||
"@spectrum-web-components/action-button": "^0.5.5-alpha.1+204701b5", | ||
"@spectrum-web-components/base": "^0.4.6-alpha.1+204701b5", | ||
"@spectrum-web-components/icon": "^0.9.9-alpha.1+204701b5", | ||
"@spectrum-web-components/icons-ui": "^0.6.9-alpha.1+204701b5", | ||
"@spectrum-web-components/shared": "^0.12.8-alpha.1+204701b5", | ||
"@spectrum-web-components/action-button": "^0.5.5", | ||
"@spectrum-web-components/base": "^0.4.5", | ||
"@spectrum-web-components/icon": "^0.9.9", | ||
"@spectrum-web-components/icons-ui": "^0.6.9", | ||
"@spectrum-web-components/shared": "^0.12.7", | ||
"tslib": "^2.0.0" | ||
@@ -69,3 +69,3 @@ }, | ||
], | ||
"gitHead": "204701b5496ec55efa8476f3a31298e7f5375212" | ||
"gitHead": "dd51b33cb741be50668d51db526200143ca48151" | ||
} |
@@ -61,2 +61,4 @@ ## Description | ||
Often an `<sp-menu>` element will be delivered inside of an `<sp-popover>` element when displaying it above other content. | ||
```html | ||
@@ -75,4 +77,88 @@ <sp-popover open style="position: relative"> | ||
## Accessibility | ||
## Managing a selection | ||
`<sp-menu>`, `<sp-menu-group>`, and `<sp-menu-item>` each deliver a different part of the wai-aria "menu" pattern and support the `menu`, `group`, and `menuitem` roles respectively. To support ease of keyboard navigation, only the first active _or_ first selected `<sp-menu-item>` can be accessed in the tab order. Once the focus has entered the menu the up and down arrow keys can be used to access the rest of the menu. | ||
The `<sp-menu>` element can be instructed to maintain a selection via the `selects` attribute. Depending on the chosen algorithm, the `<sp-menu>` element will hold a `value` property and manage the `selected` state of its `<sp-menu-item>` descendants. | ||
### selects="single" | ||
When `selects` is set to `single`, the `<sp-menu>` element will maintain one selected item after an initial selection is made. | ||
```html | ||
<p> | ||
The value of the `<sp-menu>` element is: | ||
<span id="single-value"></span> | ||
</p> | ||
<sp-menu | ||
label="Choose a shape" | ||
selects="single" | ||
onchange="this.previousElementSibling.querySelector('#single-value').textContent=this.value" | ||
> | ||
<sp-menu-item value="item-1">Square</sp-menu-item> | ||
<sp-menu-item value="item-2">Triangle</sp-menu-item> | ||
<sp-menu-item value="item-3">Parallelogram</sp-menu-item> | ||
<sp-menu-item value="item-4">Star</sp-menu-item> | ||
<sp-menu-item value="item-5">Hexagon</sp-menu-item> | ||
<sp-menu-item value="item-6" disabled>Circle</sp-menu-item> | ||
</sp-menu> | ||
``` | ||
### selects="multiple" | ||
When `selects` is set to `multiple`, the `<sp-menu>` element will maintain zero or more selected items. | ||
```html | ||
<p> | ||
The value of the `<sp-menu>` element is: | ||
<span id="multiple-value">item-3,item-4</span> | ||
</p> | ||
<sp-menu | ||
label="Choose some fruit" | ||
selects="multiple" | ||
onchange="this.previousElementSibling.querySelector('#multiple-value').textContent=this.value" | ||
> | ||
<sp-menu-item value="item-1">Apple</sp-menu-item> | ||
<sp-menu-item value="item-2">Banana</sp-menu-item> | ||
<sp-menu-item value="item-3" selected>Goji berry</sp-menu-item> | ||
<sp-menu-item value="item-4" selected>Grapes</sp-menu-item> | ||
<sp-menu-item value="item-5" disabled>Kumquat</sp-menu-item> | ||
<sp-menu-item value="item-6">Orange</sp-menu-item> | ||
</sp-menu> | ||
``` | ||
### selects="inherit" | ||
When `selects` is set to `inherit`, the `<sp-menu>` element will allow its `<sp-menu-item>` children to participate in the selection of its nearest `<sp-menu>` ancestor. | ||
```html | ||
<p> | ||
The value of the `<sp-menu>` element is: | ||
<span id="inherit-value">item-3 || item-4 || item-8 || item-11</span> | ||
</p> | ||
<sp-menu | ||
label="Choose some groceries" | ||
selects="multiple" | ||
value-separator=" || " | ||
onchange="this.previousElementSibling.querySelector('#inherit-value').textContent=this.value" | ||
> | ||
<sp-menu label="Fruit" selects="inherit"> | ||
<sp-menu-item value="item-1">Apple</sp-menu-item> | ||
<sp-menu-item value="item-2">Banana</sp-menu-item> | ||
<sp-menu-item value="item-3" selected>Goji berry</sp-menu-item> | ||
<sp-menu-item value="item-4" selected>Grapes</sp-menu-item> | ||
<sp-menu-item value="item-5" disabled>Kumquat</sp-menu-item> | ||
<sp-menu-item value="item-6">Orange</sp-menu-item> | ||
</sp-menu> | ||
<sp-menu label="Vegetables" selects="inherit"> | ||
<sp-menu-item value="item-7">Carrot</sp-menu-item> | ||
<sp-menu-item value="item-8" selected>Garlic</sp-menu-item> | ||
<sp-menu-item value="item-9" disabled>Lettuce</sp-menu-item> | ||
<sp-menu-item value="item-10">Onion</sp-menu-item> | ||
<sp-menu-item value="item-11" selected>Potato</sp-menu-item> | ||
<sp-menu-item value="item-12">Tomato</sp-menu-item> | ||
</sp-menu> | ||
</sp-menu> | ||
``` | ||
## "change" event | ||
Whether `<sp-menu>` carries a selection or not, when one of the `<sp-menu-item>` children that it manages is activated the `<sp-menu>` element will dispatch a `change` event. At dispatch time, even when a selection is not held due to the absence of the `selects` attribute, the `value` of the `<sp-menu>` will correspond to the `<sp-menu-item>` that was activated. When the `selects` attribute is present, this `value` will be persisted beyond the lifecycle of the `change` event. When `selects="multiple"` the values of multiple items will be comma separated unless otherwise set via the `value-separator` attribute. |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -14,5 +14,5 @@ /* | ||
const styles = css ` | ||
.header{display:block;margin:var(--spectrum-listitem-heading-margin);padding:var(--spectrum-listitem-heading-padding);font-size:var(--spectrum-listitem-heading-text-size);font-weight:var(--spectrum-listitem-heading-text-font-weight);line-height:var(--spectrum-listitem-heading-line-height);text-transform:var(--spectrum-listitem-heading-text-transform);letter-spacing:var(--spectrum-listitem-heading-letter-spacing);color:var(--spectrum-listheading-text-color)}:host([dir=ltr]) .header{padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150)}:host([dir=rtl]) .header{padding:0 var(--spectrum-global-dimension-size-150) 0 var(--spectrum-global-dimension-size-450)}sp-menu{display:block} | ||
.header{display:block;margin:var(--spectrum-listitem-heading-margin);padding:var(--spectrum-listitem-heading-padding);font-size:var(--spectrum-listitem-heading-text-size);font-weight:var(--spectrum-listitem-heading-text-font-weight);line-height:var(--spectrum-listitem-heading-line-height);text-transform:var(--spectrum-listitem-heading-text-transform);letter-spacing:var(--spectrum-listitem-heading-letter-spacing);color:var(--spectrum-listheading-text-color)}:host{margin:0;display:inline;overflow:visible}:host([dir=ltr]) .header{padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150)}:host([dir=rtl]) .header{padding:0 var(--spectrum-global-dimension-size-150) 0 var(--spectrum-global-dimension-size-450)}sp-menu{display:block} | ||
`; | ||
export default styles; | ||
//# sourceMappingURL=menu-group.css.js.map |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -17,5 +17,5 @@ /* | ||
var(--spectrum-alias-border-size-thin)))}:host([selected]) #selected{display:block}.icon,::slotted([slot=icon]){flex-shrink:0;align-self:flex-start}:host([dir=ltr]) .icon+#label,:host([dir=ltr]) slot[name=icon]+#label{margin-left:var(--spectrum-listitem-icon-gap)}:host([dir=rtl]) .icon+#label,:host([dir=rtl]) slot[name=icon]+#label{margin-right:var(--spectrum-listitem-icon-gap)}.icon+#label,slot[name=icon]+#label{width:calc(100% - var(--spectrum-icon-checkmark-medium-width) - var(--spectrum-listitem-icon-gap) - var(--spectrum-listitem-thumbnail-padding-left) - var(--spectrum-alias-workflow-icon-size-m, | ||
var(--spectrum-global-dimension-size-225)))}#label{flex:1 1 auto;line-height:var(--spectrum-listitem-label-line-height);-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;width:calc(100% - var(--spectrum-icon-checkmark-medium-width) - var(--spectrum-listitem-icon-gap))}:host([no-wrap]) #label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}#selected{display:none;align-self:flex-start}:host([dir=ltr]) #selected,:host([dir=ltr]) .chevron{margin-left:var(--spectrum-listitem-icon-gap)}:host([dir=rtl]) #selected,:host([dir=rtl]) .chevron{margin-right:var(--spectrum-listitem-icon-gap)}#selected{flex-grow:0;margin-top:var(--spectrum-listitem-icon-margin-top)}:host([dir=rtl]) .chevron{transform:matrix(-1,0,0,1,0,0)}:host{background-color:var(--spectrum-listitem-m-background-color,var(--spectrum-alias-background-color-transparent));color:var(--spectrum-listitem-m-text-color,var(--spectrum-alias-text-color))}:host([dir=ltr].focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=ltr].focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=ltr]:focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl].focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl].focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl]:focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host(.focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(.focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(:focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(.is-highlighted),:host(.is-open),:host(:focus),:host(:hover){background-color:var(--spectrum-listitem-m-background-color-hover,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-hover,var(--spectrum-alias-text-color))}:host([selected]){color:var(--spectrum-listitem-m-text-color-selected,var(--spectrum-alias-text-color))}:host([selected]) #selected{color:var(--spectrum-listitem-m-icon-color-selected,var(--spectrum-alias-icon-color-selected))}.is-active,:host(:active){background-color:var(--spectrum-listitem-m-background-color-down,var(--spectrum-alias-background-color-hover-overlay))}:host([disabled]){background-color:var(--spectrum-listitem-m-background-color-disabled,var(--spectrum-alias-background-color-transparent));background-image:none;color:var(--spectrum-listitem-m-text-color-disabled,var(--spectrum-alias-text-color-disabled));cursor:default}#button{position:absolute;top:0;right:0;bottom:0;left:0}::slotted([slot=value]){align-self:start}:host([dir=ltr]) ::slotted([slot=value]){margin-left:var(--spectrum-listitem-icon-gap)}:host([dir=rtl]) ::slotted([slot=value]){margin-right:var(--spectrum-listitem-icon-gap)}:host([dir=ltr]) [icon-only]::slotted(:last-of-type){margin-right:auto}:host([dir=rtl]) [icon-only]::slotted(:last-of-type){margin-left:auto} | ||
var(--spectrum-global-dimension-size-225)))}#label{flex:1 1 auto;line-height:var(--spectrum-listitem-label-line-height);-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;width:calc(100% - var(--spectrum-icon-checkmark-medium-width) - var(--spectrum-listitem-icon-gap))}:host([no-wrap]) #label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}#selected{display:none;align-self:flex-start}:host([dir=ltr]) #selected,:host([dir=ltr]) .chevron{margin-left:var(--spectrum-listitem-icon-gap)}:host([dir=rtl]) #selected,:host([dir=rtl]) .chevron{margin-right:var(--spectrum-listitem-icon-gap)}#selected{flex-grow:0;margin-top:var(--spectrum-listitem-icon-margin-top)}:host([dir=rtl]) .chevron{transform:matrix(-1,0,0,1,0,0)}:host{background-color:var(--spectrum-listitem-m-background-color,var(--spectrum-alias-background-color-transparent));color:var(--spectrum-listitem-m-text-color,var(--spectrum-alias-text-color))}:host([dir=ltr].focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=ltr].focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=ltr]:focus-visible),:host([dir=ltr][focused]){border-left-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl].focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl].focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host([dir=rtl]:focus-visible),:host([dir=rtl][focused]){border-right-color:var(--spectrum-listitem-m-focus-indicator-color,var(--spectrum-alias-border-color-focus))}:host(.focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(.focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(:focus-visible),:host([focused]){background-color:var(--spectrum-listitem-m-background-color-key-focus,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-key-focus,var(--spectrum-alias-text-color))}:host(.is-highlighted),:host(.is-open),:host(:focus),:host(:hover){background-color:var(--spectrum-listitem-m-background-color-hover,var(--spectrum-alias-background-color-hover-overlay));color:var(--spectrum-listitem-m-text-color-hover,var(--spectrum-alias-text-color))}:host([selected]){color:var(--spectrum-listitem-m-text-color-selected,var(--spectrum-alias-text-color))}:host([selected]) #selected{color:var(--spectrum-listitem-m-icon-color-selected,var(--spectrum-alias-icon-color-selected))}.is-active,:host(:active){background-color:var(--spectrum-listitem-m-background-color-down,var(--spectrum-alias-background-color-hover-overlay))}:host([disabled]){background-color:var(--spectrum-listitem-m-background-color-disabled,var(--spectrum-alias-background-color-transparent));background-image:none;color:var(--spectrum-listitem-m-text-color-disabled,var(--spectrum-alias-text-color-disabled));cursor:default}:host([hidden]){display:none}#button{position:absolute;top:0;right:0;bottom:0;left:0}::slotted([slot=value]){align-self:start}:host([dir=ltr]) ::slotted([slot=value]){margin-left:var(--spectrum-listitem-icon-gap)}:host([dir=rtl]) ::slotted([slot=value]){margin-right:var(--spectrum-listitem-icon-gap)}:host([dir=ltr]) [icon-only]::slotted(:last-of-type){margin-right:auto}:host([dir=rtl]) [icon-only]::slotted(:last-of-type){margin-left:auto} | ||
`; | ||
export default styles; | ||
//# sourceMappingURL=menu-item.css.js.map |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -14,4 +14,4 @@ /* | ||
const styles = css ` | ||
:host{--spectrum-menu-margin-x:var(--spectrum-global-dimension-size-40);--spectrum-listitem-heading-text-size:var(--spectrum-global-dimension-font-size-50);--spectrum-listitem-heading-text-font-weight:400;--spectrum-listitem-heading-text-transform:uppercase;--spectrum-listitem-heading-letter-spacing:0.06em;--spectrum-listitem-heading-margin:var(--spectrum-global-dimension-size-75) 0 0 0;--spectrum-listitem-heading-padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150);--spectrum-listitem-padding-y:var(--spectrum-global-dimension-size-85);--spectrum-listitem-icon-margin-top:var(--spectrum-global-dimension-size-50);--spectrum-listitem-label-line-height:1.3;--spectrum-listitem-heading-line-height:var(--spectrum-alias-body-text-line-height,var(--spectrum-global-font-line-height-medium));--spectrum-listitem-divider-size:var(--spectrum-listitem-m-divider-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-divider-padding:var(--spectrum-listitem-m-divider-padding,3px);--spectrum-listitem-focus-indicator-size:var(--spectrum-listitem-m-focus-indicator-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-text-font-weight:var(--spectrum-listitem-m-text-font-weight,var(--spectrum-alias-body-text-font-weight));--spectrum-listitem-text-size:var(--spectrum-listitem-m-text-size,var(--spectrum-alias-item-text-size-m));--spectrum-listitem-height:var(--spectrum-listitem-m-height,var(--spectrum-alias-item-height-m));--spectrum-listitem-icon-gap:var(--spectrum-listitem-m-icon-gap,var(--spectrum-alias-item-workflow-icon-gap-m));--spectrum-listitem-padding-left:var(--spectrum-listitem-m-padding-left,var(--spectrum-alias-item-workflow-padding-left-m));--spectrum-listitem-padding-right:var(--spectrum-listitem-m-padding-right,var(--spectrum-alias-item-padding-m));--spectrum-listitem-thumbnail-padding-left:var(--spectrum-listitem-m-thumbnail-padding-left,var(--spectrum-alias-item-padding-m));display:inline-block;box-sizing:border-box;margin-top:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-bottom:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-left:0;margin-right:0;padding:0;list-style-type:none;overflow:auto}:host([dir=ltr][selectable]) ::slotted(sp-menu-item){padding-right:var(--spectrum-listitem-selectable-padding-right)}:host([dir=rtl][selectable]) ::slotted(sp-menu-item){padding-left:var(--spectrum-listitem-selectable-padding-right)}:host([dir=ltr][selectable]) ::slotted(sp-menu-item[selected]){padding-right:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}:host([dir=rtl][selectable]) ::slotted(sp-menu-item[selected]){padding-left:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
:host{--spectrum-menu-margin-x:var(--spectrum-global-dimension-size-40);--spectrum-listitem-heading-text-size:var(--spectrum-global-dimension-font-size-50);--spectrum-listitem-heading-text-font-weight:400;--spectrum-listitem-heading-text-transform:uppercase;--spectrum-listitem-heading-letter-spacing:0.06em;--spectrum-listitem-heading-margin:var(--spectrum-global-dimension-size-75) 0 0 0;--spectrum-listitem-heading-padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150);--spectrum-listitem-padding-y:var(--spectrum-global-dimension-size-85);--spectrum-listitem-icon-margin-top:var(--spectrum-global-dimension-size-50);--spectrum-listitem-label-line-height:1.3;--spectrum-listitem-heading-line-height:var(--spectrum-alias-body-text-line-height,var(--spectrum-global-font-line-height-medium));--spectrum-listitem-divider-size:var(--spectrum-listitem-m-divider-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-divider-padding:var(--spectrum-listitem-m-divider-padding,3px);--spectrum-listitem-focus-indicator-size:var(--spectrum-listitem-m-focus-indicator-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-text-font-weight:var(--spectrum-listitem-m-text-font-weight,var(--spectrum-alias-body-text-font-weight));--spectrum-listitem-text-size:var(--spectrum-listitem-m-text-size,var(--spectrum-alias-item-text-size-m));--spectrum-listitem-height:var(--spectrum-listitem-m-height,var(--spectrum-alias-item-height-m));--spectrum-listitem-icon-gap:var(--spectrum-listitem-m-icon-gap,var(--spectrum-alias-item-workflow-icon-gap-m));--spectrum-listitem-padding-left:var(--spectrum-listitem-m-padding-left,var(--spectrum-alias-item-workflow-padding-left-m));--spectrum-listitem-padding-right:var(--spectrum-listitem-m-padding-right,var(--spectrum-alias-item-padding-m));--spectrum-listitem-thumbnail-padding-left:var(--spectrum-listitem-m-thumbnail-padding-left,var(--spectrum-alias-item-padding-m));display:inline-block;box-sizing:border-box;margin-top:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-bottom:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-left:0;margin-right:0;padding:0;list-style-type:none;overflow:auto}:host([dir=ltr][selects]) ::slotted(sp-menu-item){padding-right:var(--spectrum-listitem-selectable-padding-right)}:host([dir=rtl][selects]) ::slotted(sp-menu-item){padding-left:var(--spectrum-listitem-selectable-padding-right)}:host([dir=ltr][selects]) ::slotted(sp-menu-item[selected]){padding-right:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}:host([dir=rtl][selects]) ::slotted(sp-menu-item[selected]){padding-left:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}::slotted(sp-menu){display:block}:host{--spectrum-listheading-text-color:var(--spectrum-global-color-gray-700);background-color:var(--spectrum-listitem-m-background-color,var(--spectrum-alias-background-color-transparent));--spectrum-listitem-selectable-padding-right:calc(var(--spectrum-global-dimension-size-100) + var(--spectrum-icon-checkmark-medium-width) + var(--spectrum-listitem-icon-gap))}:host(:focus){outline:none}:host sp-menu{display:block} | ||
@@ -18,0 +18,0 @@ `; |
import { SpectrumElement, CSSResultArray, TemplateResult, PropertyValues } from '@spectrum-web-components/base'; | ||
import { MenuItem } from './MenuItem.js'; | ||
export interface MenuQueryRoleEventDetail { | ||
role: string; | ||
export interface MenuChildItem { | ||
menuItem: MenuItem; | ||
managed: boolean; | ||
active: boolean; | ||
focusable: boolean; | ||
focusRoot: Menu; | ||
} | ||
@@ -9,10 +13,26 @@ /** | ||
* @element sp-menu | ||
* | ||
* @fires change - Announces that the `value` of the element has changed | ||
* @attr selects - whether the element has a specific selection algorithm that it applies | ||
* to its item descendants. `single` allows only one descendent to be selected at a time. | ||
* `multiple` allows many descendants to be selected. `inherit` will be applied dynamically | ||
* when an ancestor of this element is actively managing the selection of its descendents. | ||
* When the `selects` attribute is not present a `value` will not be maintained and the Menu | ||
* Item children of this Menu will not have their `selected` state managed. | ||
*/ | ||
export declare class Menu extends SpectrumElement { | ||
static get styles(): CSSResultArray; | ||
selectable: boolean; | ||
menuItems: MenuItem[]; | ||
label: string; | ||
selects: undefined | 'inherit' | 'single' | 'multiple'; | ||
value: string; | ||
valueSeparator: string; | ||
selected: string[]; | ||
selectedItems: MenuItem[]; | ||
menuSlot: HTMLSlotElement; | ||
private childItemSet; | ||
focusedItemIndex: number; | ||
focusInItemIndex: number; | ||
private selectedItemsMap; | ||
get childItems(): MenuItem[]; | ||
private cachedChildItems; | ||
private updateCachedMenuItems; | ||
/** | ||
@@ -26,2 +46,23 @@ * Hide this getter from web-component-analyzer until | ||
get childRole(): string; | ||
protected get ownRole(): string; | ||
private resolvedSelects?; | ||
private resolvedRole?; | ||
/** | ||
* When a descendant `<sp-menu-item>` element is added or updated it will dispatch | ||
* this event to announce its presence in the DOM. During the capture phase the first | ||
* Menu based element that the event encounters will manage the focus state of the | ||
* dispatching `<sp-menu-item>` element. | ||
* @param event | ||
*/ | ||
private onFocusableItemAddedOrUpdated; | ||
/** | ||
* When a descendant `<sp-menu-item>` element is added or updated it will dispatch | ||
* this event to announce its presence in the DOM. During the bubble phase the first | ||
* Menu based element that the event encounters that does not inherit selection will | ||
* manage the selection state of the dispatching `<sp-menu-item>` element. | ||
* @param event | ||
*/ | ||
private onSelectableItemAddedOrUpdated; | ||
private addChildItem; | ||
private removeChildItem; | ||
constructor(); | ||
@@ -31,4 +72,5 @@ focus({ preventScroll }?: FocusOptions): void; | ||
startListeningToKeyboard(): void; | ||
handleFocusout(): void; | ||
handleFocusout(event: FocusEvent): void; | ||
stopListeningToKeyboard(): void; | ||
selectOrToggleItem(targetItem: MenuItem): Promise<void>; | ||
handleKeydown(event: KeyboardEvent): void; | ||
@@ -38,14 +80,15 @@ focusMenuItemByOffset(offset: number): MenuItem; | ||
updateSelectedItemIndex(): void; | ||
private prepItems; | ||
private forwardFocusVisibleToitem; | ||
private _willUpdateItems; | ||
private handleItemsChanged; | ||
private updateItemFocus; | ||
private forwardFocusVisibleToItem; | ||
render(): TemplateResult; | ||
private _notFirstUpdated; | ||
protected firstUpdated(changed: PropertyValues): void; | ||
protected updated(changes: PropertyValues<this>): void; | ||
protected selectsChanged(): void; | ||
connectedCallback(): void; | ||
disconnectedCallback(): void; | ||
private observer; | ||
protected childItemsUpdated: Promise<unknown[]>; | ||
protected cacheUpdated: Promise<void>; | ||
protected _getUpdateComplete(): Promise<boolean>; | ||
} | ||
declare global { | ||
interface GlobalEventHandlersEventMap { | ||
'sp-menu-query-role': CustomEvent<MenuQueryRoleEventDetail>; | ||
} | ||
} |
437
src/Menu.js
@@ -13,3 +13,4 @@ /* | ||
import { __decorate } from "tslib"; | ||
import { html, SpectrumElement, property, } from '@spectrum-web-components/base'; | ||
import { html, SpectrumElement, property, query, } from '@spectrum-web-components/base'; | ||
import { MenuItem, } from './MenuItem.js'; | ||
import menuStyles from './menu.css.js'; | ||
@@ -19,3 +20,9 @@ /** | ||
* @element sp-menu | ||
* | ||
* @fires change - Announces that the `value` of the element has changed | ||
* @attr selects - whether the element has a specific selection algorithm that it applies | ||
* to its item descendants. `single` allows only one descendent to be selected at a time. | ||
* `multiple` allows many descendants to be selected. `inherit` will be applied dynamically | ||
* when an ancestor of this element is actively managing the selection of its descendents. | ||
* When the `selects` attribute is not present a `value` will not be maintained and the Menu | ||
* Item children of this Menu will not have their `selected` state managed. | ||
*/ | ||
@@ -25,26 +32,25 @@ export class Menu extends SpectrumElement { | ||
super(); | ||
this.selectable = false; | ||
this.menuItems = []; | ||
this.label = ''; | ||
this.value = ''; | ||
// For the multiple select case, we'll join the value strings together | ||
// for the value property with this separator | ||
this.valueSeparator = ','; | ||
// TODO: which of these to keep? | ||
// TODO: allow setting this in the API to change the values | ||
this.selected = []; | ||
this.selectedItems = []; | ||
this.childItemSet = new Set(); | ||
this.focusedItemIndex = 0; | ||
this.focusInItemIndex = 0; | ||
this.prepItems = () => { | ||
this.menuItems = [ | ||
...this.querySelectorAll(`[role="${this.childRole}"]`), | ||
]; | ||
if (!this.menuItems || this.menuItems.length === 0) { | ||
return; | ||
} | ||
this.updateSelectedItemIndex(); | ||
const focusInItem = this.menuItems[this.focusInItemIndex]; | ||
if (this.getRootNode().activeElement === this) { | ||
this.forwardFocusVisibleToitem(focusInItem); | ||
} | ||
}; | ||
this.handleKeydown = this.handleKeydown.bind(this); | ||
this.startListeningToKeyboard = this.startListeningToKeyboard.bind(this); | ||
this.stopListeningToKeyboard = this.stopListeningToKeyboard.bind(this); | ||
this.onClick = this.onClick.bind(this); | ||
this.selectedItemsMap = new Map(); | ||
this._willUpdateItems = false; | ||
this._notFirstUpdated = false; | ||
this.cacheUpdated = Promise.resolve(); | ||
this.addEventListener('sp-menu-item-added-or-updated', this.onSelectableItemAddedOrUpdated); | ||
this.addEventListener('sp-menu-item-added-or-updated', this.onFocusableItemAddedOrUpdated, { | ||
capture: true, | ||
}); | ||
this.addEventListener('sp-menu-item-removed', this.removeChildItem); | ||
this.addEventListener('click', this.onClick); | ||
this.addEventListener('focusin', this.startListeningToKeyboard); | ||
this.addEventListener('focus', () => this.focus({ preventScroll: true })); | ||
} | ||
@@ -54,2 +60,23 @@ static get styles() { | ||
} | ||
get childItems() { | ||
if (!this.cachedChildItems) { | ||
this.cachedChildItems = this.updateCachedMenuItems(); | ||
} | ||
return this.cachedChildItems; | ||
} | ||
updateCachedMenuItems() { | ||
this.cachedChildItems = []; | ||
const slotElements = this.menuSlot.assignedElements({ flatten: true }); | ||
for (const slotElement of slotElements) { | ||
const childMenuItems = slotElement instanceof MenuItem | ||
? [slotElement] | ||
: [...slotElement.querySelectorAll(`*`)]; | ||
for (const childMenuItem of childMenuItems) { | ||
if (this.childItemSet.has(childMenuItem)) { | ||
this.cachedChildItems.push(childMenuItem); | ||
} | ||
} | ||
} | ||
return this.cachedChildItems; | ||
} | ||
/** | ||
@@ -63,9 +90,80 @@ * Hide this getter from web-component-analyzer until | ||
get childRole() { | ||
return this.getAttribute('role') === 'menu' ? 'menuitem' : 'option'; | ||
if (this.resolvedRole === 'listbox') { | ||
return 'option'; | ||
} | ||
switch (this.resolvedSelects) { | ||
case 'single': | ||
return 'menuitemradio'; | ||
case 'multiple': | ||
return 'menuitemcheckbox'; | ||
default: | ||
return 'menuitem'; | ||
} | ||
} | ||
get ownRole() { | ||
return 'menu'; | ||
} | ||
/** | ||
* When a descendant `<sp-menu-item>` element is added or updated it will dispatch | ||
* this event to announce its presence in the DOM. During the capture phase the first | ||
* Menu based element that the event encounters will manage the focus state of the | ||
* dispatching `<sp-menu-item>` element. | ||
* @param event | ||
*/ | ||
onFocusableItemAddedOrUpdated(event) { | ||
var _a; | ||
event.focusRoot = this; | ||
this.addChildItem(event.item); | ||
if (this.selects === 'inherit') { | ||
this.resolvedSelects = 'inherit'; | ||
this.resolvedRole = (((_a = event.currentAncestorWithSelects) === null || _a === void 0 ? void 0 : _a.getAttribute('role')) || | ||
this.getAttribute('role') || | ||
undefined); | ||
} | ||
else if (this.selects) { | ||
this.resolvedRole = (this.getAttribute('role') || | ||
undefined); | ||
this.resolvedSelects = this.selects; | ||
event.currentAncestorWithSelects = this; | ||
} | ||
else { | ||
this.resolvedRole = (this.getAttribute('role') || | ||
undefined); | ||
this.resolvedSelects = | ||
this.resolvedRole === 'none' ? 'ignore' : 'none'; | ||
} | ||
} | ||
/** | ||
* When a descendant `<sp-menu-item>` element is added or updated it will dispatch | ||
* this event to announce its presence in the DOM. During the bubble phase the first | ||
* Menu based element that the event encounters that does not inherit selection will | ||
* manage the selection state of the dispatching `<sp-menu-item>` element. | ||
* @param event | ||
*/ | ||
onSelectableItemAddedOrUpdated(event) { | ||
const selects = this.resolvedSelects === 'single' || | ||
this.resolvedSelects === 'multiple'; | ||
if ((selects || (!this.selects && this.resolvedSelects !== 'ignore')) && | ||
!event.item.menuData.selectionRoot) { | ||
event.item.setRole(this.childRole); | ||
event.selectionRoot = this; | ||
} | ||
} | ||
addChildItem(item) { | ||
this.childItemSet.add(item); | ||
this.handleItemsChanged(); | ||
} | ||
removeChildItem(event) { | ||
this.childItemSet.delete(event.item); | ||
this.cachedChildItems = undefined; | ||
} | ||
focus({ preventScroll } = {}) { | ||
if (!this.menuItems.length || | ||
this.menuItems.every((item) => item.disabled)) { | ||
if (!this.childItems.length || | ||
this.childItems.every((childItem) => childItem.disabled)) { | ||
return; | ||
} | ||
if (this.childItems.some((childItem) => childItem.menuData.focusRoot !== this)) { | ||
super.focus({ preventScroll }); | ||
return; | ||
} | ||
this.focusMenuItemByOffset(0); | ||
@@ -79,2 +177,5 @@ super.focus({ preventScroll }); | ||
onClick(event) { | ||
if (event.defaultPrevented) { | ||
return; | ||
} | ||
const path = event.composedPath(); | ||
@@ -88,4 +189,7 @@ const target = path.find((el) => { | ||
}); | ||
/* c8 ignore next 3 */ | ||
if (!target) { | ||
if ((target === null || target === void 0 ? void 0 : target.menuData.selectionRoot) === this) { | ||
event.preventDefault(); | ||
this.selectOrToggleItem(target); | ||
} | ||
else { | ||
return; | ||
@@ -96,7 +200,13 @@ } | ||
startListeningToKeyboard() { | ||
var _a; | ||
if (this.childItems.some((childItem) => childItem.menuData.focusRoot !== this)) { | ||
return; | ||
} | ||
const activeElement = this.getRootNode().activeElement; | ||
if (activeElement !== this) { | ||
this.focus({ preventScroll: true }); | ||
const selectionRoot = ((_a = this.childItems[this.focusedItemIndex]) === null || _a === void 0 ? void 0 : _a.menuData.selectionRoot) || | ||
this; | ||
if (activeElement !== selectionRoot) { | ||
selectionRoot.focus({ preventScroll: true }); | ||
if (activeElement && this.focusedItemIndex === 0) { | ||
const offset = this.menuItems.indexOf(activeElement); | ||
const offset = this.childItems.findIndex((childItem) => childItem === activeElement); | ||
if (offset > 0) { | ||
@@ -110,7 +220,10 @@ this.focusMenuItemByOffset(offset); | ||
} | ||
handleFocusout() { | ||
handleFocusout(event) { | ||
this.stopListeningToKeyboard(); | ||
const focusedItem = this.menuItems[this.focusedItemIndex]; | ||
if (focusedItem) { | ||
focusedItem.focused = false; | ||
if (event.target === this && | ||
this.childItems.some((childItem) => childItem.menuData.focusRoot === this)) { | ||
const focusedItem = this.childItems[this.focusedItemIndex]; | ||
if (focusedItem) { | ||
focusedItem.focused = false; | ||
} | ||
} | ||
@@ -121,3 +234,68 @@ } | ||
} | ||
async selectOrToggleItem(targetItem) { | ||
const resolvedSelects = this.resolvedSelects; | ||
const oldSelectedItemsMap = new Map(this.selectedItemsMap); | ||
const oldSelected = this.selected.slice(); | ||
const oldSelectedItems = this.selectedItems.slice(); | ||
const oldValue = this.value; | ||
if (resolvedSelects === 'multiple') { | ||
if (this.selectedItemsMap.has(targetItem)) { | ||
this.selectedItemsMap.delete(targetItem); | ||
} | ||
else { | ||
this.selectedItemsMap.set(targetItem, true); | ||
} | ||
// Match HTML select and set the first selected | ||
// item as the value. Also set the selected array | ||
// in the order of the menu items. | ||
const selected = []; | ||
const selectedItems = []; | ||
this.childItemSet.forEach((childItem) => { | ||
if (childItem.menuData.selectionRoot !== this) | ||
return; | ||
if (this.selectedItemsMap.has(childItem)) { | ||
selected.push(childItem.value); | ||
selectedItems.push(childItem); | ||
} | ||
}); | ||
this.selected = selected; | ||
this.selectedItems = selectedItems; | ||
this.value = this.selected.join(this.valueSeparator); | ||
} | ||
else { | ||
this.selectedItemsMap.clear(); | ||
this.selectedItemsMap.set(targetItem, true); | ||
this.value = targetItem.value; | ||
this.selected = [targetItem.value]; | ||
this.selectedItems = [targetItem]; | ||
} | ||
await this.updateComplete; | ||
const applyDefault = this.dispatchEvent(new Event('change', { | ||
cancelable: true, | ||
bubbles: true, | ||
composed: true, | ||
})); | ||
if (!applyDefault) { | ||
// Cancel the event & don't apply the selection | ||
this.selected = oldSelected; | ||
this.selectedItems = oldSelectedItems; | ||
this.selectedItemsMap = oldSelectedItemsMap; | ||
this.value = oldValue; | ||
return; | ||
} | ||
// Apply the selection changes to the menu items | ||
if (resolvedSelects === 'single') { | ||
for (const oldItem of oldSelectedItemsMap.keys()) { | ||
if (oldItem !== targetItem) { | ||
oldItem.selected = false; | ||
} | ||
} | ||
targetItem.selected = true; | ||
} | ||
else if (resolvedSelects === 'multiple') { | ||
targetItem.selected = !targetItem.selected; | ||
} | ||
} | ||
handleKeydown(event) { | ||
var _a; | ||
const { code } = event; | ||
@@ -129,3 +307,3 @@ if (code === 'Tab') { | ||
if (code === 'Space' || code === 'Enter') { | ||
this.menuItems[this.focusedItemIndex].click(); | ||
(_a = this.childItems[this.focusedItemIndex]) === null || _a === void 0 ? void 0 : _a.click(); | ||
return; | ||
@@ -136,3 +314,3 @@ } | ||
} | ||
const lastFocusedItem = this.menuItems[this.focusedItemIndex]; | ||
const lastFocusedItem = this.childItems[this.focusedItemIndex]; | ||
const direction = code === 'ArrowDown' ? 1 : -1; | ||
@@ -148,9 +326,9 @@ const itemToFocus = this.focusMenuItemByOffset(direction); | ||
const step = offset || 1; | ||
const focusedItem = this.menuItems[this.focusedItemIndex]; | ||
const focusedItem = this.childItems[this.focusedItemIndex]; | ||
focusedItem.focused = false; | ||
this.focusedItemIndex = | ||
(this.menuItems.length + this.focusedItemIndex + offset) % | ||
this.menuItems.length; | ||
let itemToFocus = this.menuItems[this.focusedItemIndex]; | ||
let availableItems = this.menuItems.length; | ||
(this.childItems.length + this.focusedItemIndex + offset) % | ||
this.childItems.length; | ||
let itemToFocus = this.childItems[this.focusedItemIndex]; | ||
let availableItems = this.childItems.length; | ||
// cycle through the available items in the directions of the offset to find the next non-disabled item | ||
@@ -160,10 +338,9 @@ while (itemToFocus.disabled && availableItems) { | ||
this.focusedItemIndex = | ||
(this.menuItems.length + this.focusedItemIndex + step) % | ||
this.menuItems.length; | ||
itemToFocus = this.menuItems[this.focusedItemIndex]; | ||
(this.childItems.length + this.focusedItemIndex + step) % | ||
this.childItems.length; | ||
itemToFocus = this.childItems[this.focusedItemIndex]; | ||
} | ||
// if there are no non-disabled items, skip the work to focus a child | ||
if (!itemToFocus.disabled) { | ||
this.forwardFocusVisibleToitem(itemToFocus); | ||
this.setAttribute('aria-activedescendant', itemToFocus.id); | ||
if (!(itemToFocus === null || itemToFocus === void 0 ? void 0 : itemToFocus.disabled)) { | ||
this.forwardFocusVisibleToItem(itemToFocus); | ||
} | ||
@@ -175,9 +352,7 @@ return itemToFocus; | ||
requestAnimationFrame(() => { | ||
/* c8 ignore next 3 */ | ||
if (this.menuItems.length === 0) { | ||
return; | ||
const focusedItem = this.childItems[this.focusedItemIndex]; | ||
if (focusedItem) { | ||
focusedItem.focused = false; | ||
this.updateSelectedItemIndex(); | ||
} | ||
const focusedItem = this.menuItems[this.focusedItemIndex]; | ||
focusedItem.focused = false; | ||
this.updateSelectedItemIndex(); | ||
}); | ||
@@ -187,18 +362,67 @@ }, { once: true }); | ||
updateSelectedItemIndex() { | ||
let index = this.menuItems.length - 1; | ||
let item = this.menuItems[index]; | ||
let index = this.childItems.length - 1; | ||
let item = this.childItems[index]; | ||
while (index && item && !item.selected) { | ||
index -= 1; | ||
item = this.menuItems[index]; | ||
item = this.childItems[index]; | ||
} | ||
index = Math.max(index, 0); | ||
this.menuItems.forEach((item, i) => { | ||
const selectedItemsMap = new Map(); | ||
const selected = []; | ||
const selectedItems = []; | ||
this.childItems.forEach((childItem, i) => { | ||
if (childItem.menuData.selectionRoot !== this) | ||
return; | ||
if (childItem.selected) { | ||
selectedItemsMap.set(childItem, true); | ||
selected.push(childItem.value); | ||
selectedItems.push(childItem); | ||
} | ||
if (i !== index) { | ||
item.focused = false; | ||
childItem.focused = false; | ||
} | ||
}); | ||
this.selectedItemsMap = selectedItemsMap; | ||
this.selected = selected; | ||
this.selectedItems = selectedItems; | ||
this.value = this.selected.join(this.valueSeparator); | ||
this.focusedItemIndex = index; | ||
this.focusInItemIndex = index; | ||
} | ||
forwardFocusVisibleToitem(item) { | ||
handleItemsChanged() { | ||
this.cachedChildItems = undefined; | ||
if (!this._willUpdateItems) { | ||
/* c8 ignore next 3 */ | ||
let resolve = () => { | ||
return; | ||
}; | ||
this.cacheUpdated = new Promise((res) => (resolve = res)); | ||
this._willUpdateItems = true; | ||
// Debounce the update so we only update once | ||
// if multiple items have changed | ||
window.requestAnimationFrame(() => { | ||
if (this.cachedChildItems === undefined) { | ||
this.updateSelectedItemIndex(); | ||
this.updateItemFocus(); | ||
} | ||
this._willUpdateItems = false; | ||
resolve(); | ||
}); | ||
} | ||
} | ||
updateItemFocus() { | ||
if (this.childItems.length == 0) { | ||
return; | ||
} | ||
const focusInItem = this.childItems[this.focusInItemIndex]; | ||
if (this.getRootNode().activeElement === | ||
focusInItem.menuData.focusRoot) { | ||
this.forwardFocusVisibleToItem(focusInItem); | ||
} | ||
} | ||
forwardFocusVisibleToItem(item) { | ||
const activeElement = this.getRootNode().activeElement; | ||
if (item.menuData.focusRoot !== this) { | ||
return; | ||
} | ||
let shouldFocus = false; | ||
@@ -211,9 +435,13 @@ try { | ||
shouldFocus = | ||
this.matches(':focus-visible') || | ||
this.matches('.focus-visible'); | ||
activeElement.matches(':focus-visible') || | ||
activeElement.matches('.focus-visible'); | ||
} | ||
catch (error) { | ||
shouldFocus = this.matches('.focus-visible'); | ||
shouldFocus = activeElement.matches('.focus-visible'); | ||
} | ||
item.focused = shouldFocus; | ||
if (item.menuData.selectionRoot && | ||
item.menuData.selectionRoot !== this) { | ||
item.menuData.selectionRoot.focus(); | ||
} | ||
} | ||
@@ -227,33 +455,78 @@ render() { | ||
super.firstUpdated(changed); | ||
if (this.getAttribute('role') !== 'presentation') { | ||
const role = this.getAttribute('role'); | ||
if (role === 'group') { | ||
this.tabIndex = -1; | ||
} | ||
else if (role !== 'none') { | ||
this.tabIndex = 0; | ||
} | ||
const updates = [ | ||
new Promise((res) => requestAnimationFrame(() => res(true))), | ||
]; | ||
[...this.children].forEach((item) => { | ||
if (item.localName === 'sp-menu-item') { | ||
updates.push(item.updateComplete); | ||
} | ||
}); | ||
this.childItemsUpdated = Promise.all(updates); | ||
} | ||
updated(changes) { | ||
super.updated(changes); | ||
if (changes.has('selects') && this._notFirstUpdated) { | ||
this.selectsChanged(); | ||
} | ||
if (changes.has('label')) { | ||
if (this.label) { | ||
this.setAttribute('aria-label', this.label); | ||
} | ||
else { | ||
this.removeAttribute('aria-label'); | ||
} | ||
} | ||
this._notFirstUpdated = true; | ||
} | ||
selectsChanged() { | ||
const updates = [ | ||
new Promise((res) => requestAnimationFrame(() => res(true))), | ||
]; | ||
this.childItemSet.forEach((childItem) => { | ||
updates.push(childItem.triggerUpdate()); | ||
}); | ||
this.childItemsUpdated = Promise.all(updates); | ||
} | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
if (!this.hasAttribute('role')) { | ||
const queryRoleEvent = new CustomEvent('sp-menu-query-role', { | ||
bubbles: true, | ||
composed: true, | ||
detail: { | ||
role: '', | ||
}, | ||
}); | ||
this.dispatchEvent(queryRoleEvent); | ||
this.setAttribute('role', queryRoleEvent.detail.role || 'menu'); | ||
this.setAttribute('role', this.ownRole); | ||
} | ||
if (!this.observer) { | ||
this.observer = new MutationObserver(this.prepItems); | ||
} | ||
this.observer.observe(this, { childList: true, subtree: true }); | ||
this.updateComplete.then(() => this.prepItems()); | ||
this.updateComplete.then(() => this.updateItemFocus()); | ||
} | ||
disconnectedCallback() { | ||
this.observer.disconnect(); | ||
super.disconnectedCallback(); | ||
async _getUpdateComplete() { | ||
const complete = (await super._getUpdateComplete()); | ||
await this.childItemsUpdated; | ||
await this.cacheUpdated; | ||
return complete; | ||
} | ||
} | ||
__decorate([ | ||
property({ type: Boolean, reflect: true }) | ||
], Menu.prototype, "selectable", void 0); | ||
property({ type: String, reflect: true }) | ||
], Menu.prototype, "label", void 0); | ||
__decorate([ | ||
property({ type: String, reflect: true }) | ||
], Menu.prototype, "selects", void 0); | ||
__decorate([ | ||
property({ type: String }) | ||
], Menu.prototype, "value", void 0); | ||
__decorate([ | ||
property({ type: String, attribute: 'value-separator' }) | ||
], Menu.prototype, "valueSeparator", void 0); | ||
__decorate([ | ||
property({ attribute: false }) | ||
], Menu.prototype, "selected", void 0); | ||
__decorate([ | ||
property({ attribute: false }) | ||
], Menu.prototype, "selectedItems", void 0); | ||
__decorate([ | ||
query('slot:not([name])') | ||
], Menu.prototype, "menuSlot", void 0); | ||
//# sourceMappingURL=Menu.js.map |
@@ -1,2 +0,3 @@ | ||
import { SpectrumElement, CSSResultArray, TemplateResult } from '@spectrum-web-components/base'; | ||
import { CSSResultArray, TemplateResult } from '@spectrum-web-components/base'; | ||
import { Menu } from './Menu.js'; | ||
import '../sp-menu.js'; | ||
@@ -10,9 +11,12 @@ /** | ||
*/ | ||
export declare class MenuGroup extends SpectrumElement { | ||
export declare class MenuGroup extends Menu { | ||
static get styles(): CSSResultArray; | ||
private instanceCount; | ||
private static instances; | ||
private headerId; | ||
constructor(); | ||
private headerElements; | ||
private headerElement?; | ||
protected get ownRole(): string; | ||
protected updateLabel(): void; | ||
render(): TemplateResult; | ||
protected firstUpdated(): void; | ||
} |
@@ -12,3 +12,5 @@ /* | ||
*/ | ||
import { html, SpectrumElement, } from '@spectrum-web-components/base'; | ||
import { __decorate } from "tslib"; | ||
import { html, queryAssignedNodes, } from '@spectrum-web-components/base'; | ||
import { Menu } from './Menu.js'; | ||
import '../sp-menu.js'; | ||
@@ -23,18 +25,48 @@ import menuGroupStyles from './menu-group.css.js'; | ||
*/ | ||
export class MenuGroup extends SpectrumElement { | ||
export class MenuGroup extends Menu { | ||
constructor() { | ||
super(); | ||
this.instanceCount = MenuGroup.instances; | ||
MenuGroup.instances += 1; | ||
this.headerId = `sp-menu-group-label-${MenuGroup.instances}`; | ||
} | ||
static get styles() { | ||
return [menuGroupStyles]; | ||
return [...super.styles, menuGroupStyles]; | ||
} | ||
get ownRole() { | ||
switch (this.selects) { | ||
case 'multiple': | ||
case 'single': | ||
case 'inherit': | ||
return 'group'; | ||
default: | ||
return 'menu'; | ||
} | ||
} | ||
updateLabel() { | ||
const headerElement = this.headerElements.length | ||
? this.headerElements[0] | ||
: undefined; | ||
if (headerElement !== this.headerElement) { | ||
if (this.headerElement && this.headerElement.id === this.headerId) { | ||
this.headerElement.removeAttribute('id'); | ||
} | ||
if (headerElement) { | ||
const headerId = headerElement.id || this.headerId; | ||
if (!headerElement.id) { | ||
headerElement.id = headerId; | ||
} | ||
this.setAttribute('aria-labelledby', headerId); | ||
} | ||
else { | ||
this.removeAttribute('aria-labelledby'); | ||
} | ||
} | ||
this.headerElement = headerElement; | ||
} | ||
render() { | ||
const labelledby = `menu-heading-category-${this.instanceCount}`; | ||
return html ` | ||
<span class="header" id=${labelledby} aria-hidden="true"> | ||
<slot name="header"></slot> | ||
<span class="header" aria-hidden="true"> | ||
<slot name="header" @slotchange=${this.updateLabel}></slot> | ||
</span> | ||
<sp-menu role="presentation"> | ||
<sp-menu role="none"> | ||
<slot></slot> | ||
@@ -44,7 +76,7 @@ </sp-menu> | ||
} | ||
firstUpdated() { | ||
this.setAttribute('role', 'none'); | ||
} | ||
} | ||
MenuGroup.instances = 0; | ||
__decorate([ | ||
queryAssignedNodes('header', true) | ||
], MenuGroup.prototype, "headerElements", void 0); | ||
//# sourceMappingURL=MenuGroup.js.map |
import { CSSResultArray, TemplateResult, PropertyValues } from '@spectrum-web-components/base'; | ||
import '@spectrum-web-components/icons-ui/icons/sp-icon-checkmark100.js'; | ||
import { ActionButton } from '@spectrum-web-components/action-button'; | ||
export interface MenuItemQueryRoleEventDetail { | ||
role: string; | ||
import { Menu } from './Menu.js'; | ||
export declare class MenuItemRemovedEvent extends Event { | ||
constructor(); | ||
get item(): MenuItem; | ||
_item?: MenuItem; | ||
reset(): void; | ||
} | ||
export declare class MenuItemAddedOrUpdatedEvent extends Event { | ||
constructor(); | ||
set focusRoot(root: Menu); | ||
set selectionRoot(root: Menu); | ||
get item(): MenuItem; | ||
_item?: MenuItem; | ||
set currentAncestorWithSelects(ancestor: Menu | undefined); | ||
get currentAncestorWithSelects(): Menu | undefined; | ||
_currentAncestorWithSelects?: Menu; | ||
reset(item: MenuItem): void; | ||
} | ||
/** | ||
@@ -11,2 +26,4 @@ * Spectrum Menu Item Component | ||
* @slot value - content placed at the end of the Menu Item like values, keyboard shortcuts, etc. | ||
* @fires sp-menu-item-added - announces the item has been added so a parent menu can take ownerships | ||
* @fires sp-menu-item-removed - announces when removed from the DOM so the parent menu can remove ownership and update selected state | ||
*/ | ||
@@ -25,9 +42,18 @@ export declare class MenuItem extends ActionButton { | ||
protected firstUpdated(changes: PropertyValues): void; | ||
updateAriaSelected(): void; | ||
setRole(role: string): void; | ||
protected updated(changes: PropertyValues): void; | ||
connectedCallback(): void; | ||
disconnectedCallback(): void; | ||
triggerUpdate(): Promise<void>; | ||
menuData: { | ||
focusRoot?: Menu; | ||
selectionRoot?: Menu; | ||
}; | ||
} | ||
declare global { | ||
interface GlobalEventHandlersEventMap { | ||
'sp-menu-item-query-role': CustomEvent<MenuItemQueryRoleEventDetail>; | ||
'sp-menu-item-added-or-updated': MenuItemAddedOrUpdatedEvent; | ||
'sp-menu-item-removed': MenuItemRemovedEvent; | ||
} | ||
} |
@@ -18,2 +18,56 @@ /* | ||
import checkmarkStyles from '@spectrum-web-components/icon/src/spectrum-icon-checkmark.css.js'; | ||
export class MenuItemRemovedEvent extends Event { | ||
constructor() { | ||
super('sp-menu-item-removed', { | ||
bubbles: true, | ||
composed: true, | ||
}); | ||
} | ||
get item() { | ||
if (!this._item) { | ||
this._item = this.composedPath()[0]; | ||
} | ||
return this._item; | ||
} | ||
reset() { | ||
this._item = undefined; | ||
} | ||
} | ||
export class MenuItemAddedOrUpdatedEvent extends Event { | ||
constructor() { | ||
super('sp-menu-item-added-or-updated', { | ||
bubbles: true, | ||
composed: true, | ||
}); | ||
} | ||
set focusRoot(root) { | ||
this.item.menuData.focusRoot = this.item.menuData.focusRoot || root; | ||
} | ||
set selectionRoot(root) { | ||
this.item.menuData.selectionRoot = | ||
this.item.menuData.selectionRoot || root; | ||
} | ||
get item() { | ||
if (!this._item) { | ||
this._item = this.composedPath()[0]; | ||
} | ||
return this._item; | ||
} | ||
set currentAncestorWithSelects(ancestor) { | ||
this._currentAncestorWithSelects = ancestor; | ||
} | ||
get currentAncestorWithSelects() { | ||
return this._currentAncestorWithSelects; | ||
} | ||
reset(item) { | ||
this._item = undefined; | ||
this._currentAncestorWithSelects = undefined; | ||
item.menuData = { | ||
focusRoot: undefined, | ||
selectionRoot: undefined, | ||
}; | ||
} | ||
} | ||
const addOrUpdateEvent = new MenuItemAddedOrUpdatedEvent(); | ||
const removeEvent = new MenuItemRemovedEvent(); | ||
/** | ||
@@ -23,2 +77,4 @@ * Spectrum Menu Item Component | ||
* @slot value - content placed at the end of the Menu Item like values, keyboard shortcuts, etc. | ||
* @fires sp-menu-item-added - announces the item has been added so a parent menu can take ownerships | ||
* @fires sp-menu-item-removed - announces when removed from the DOM so the parent menu can remove ownership and update selected state | ||
*/ | ||
@@ -30,2 +86,6 @@ export class MenuItem extends ActionButton { | ||
this.noWrap = false; | ||
this.menuData = { | ||
focusRoot: undefined, | ||
selectionRoot: undefined, | ||
}; | ||
} | ||
@@ -78,6 +138,19 @@ static get styles() { | ||
} | ||
updateAriaSelected() { | ||
const role = this.getAttribute('role'); | ||
if (role === 'option') { | ||
this.setAttribute('aria-selected', this.selected ? 'true' : 'false'); | ||
} | ||
else if (role === 'menuitemcheckbox' || role === 'menuitemradio') { | ||
this.setAttribute('aria-checked', this.selected ? 'true' : 'false'); | ||
} | ||
} | ||
setRole(role) { | ||
this.setAttribute('role', role); | ||
this.updateAriaSelected(); | ||
} | ||
updated(changes) { | ||
super.updated(changes); | ||
if (this.getAttribute('role') === 'option' && changes.has('selected')) { | ||
this.setAttribute('aria-selected', this.selected ? 'true' : 'false'); | ||
if (changes.has('selected')) { | ||
this.updateAriaSelected(); | ||
} | ||
@@ -87,14 +160,15 @@ } | ||
super.connectedCallback(); | ||
if (!this.hasAttribute('role')) { | ||
const queryRoleEvent = new CustomEvent('sp-menu-item-query-role', { | ||
bubbles: true, | ||
composed: true, | ||
detail: { | ||
role: '', | ||
}, | ||
}); | ||
this.dispatchEvent(queryRoleEvent); | ||
this.setAttribute('role', queryRoleEvent.detail.role || 'menuitem'); | ||
} | ||
addOrUpdateEvent.reset(this); | ||
this.dispatchEvent(addOrUpdateEvent); | ||
} | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
removeEvent.reset(); | ||
this.dispatchEvent(removeEvent); | ||
} | ||
async triggerUpdate() { | ||
await new Promise((ready) => requestAnimationFrame(ready)); | ||
addOrUpdateEvent.reset(this); | ||
this.dispatchEvent(addOrUpdateEvent); | ||
} | ||
} | ||
@@ -101,0 +175,0 @@ MenuItem.instanceCount = 0; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -14,5 +14,5 @@ /* | ||
const styles = css ` | ||
.checkmark{transform:scale(1);opacity:1;display:none;align-self:flex-start}:host([dir=ltr]) .checkmark{margin-left:var(--spectrum-listitem-texticon-icon-gap)}:host([dir=rtl]) .checkmark{margin-right:var(--spectrum-listitem-texticon-icon-gap)}.checkmark{flex-grow:0;margin-top:var(--spectrum-listitem-texticon-icon-margin-top)} | ||
.checkmark{transform:scale(1);opacity:1;display:none;align-self:flex-start}:host([dir=ltr]) .checkmark{margin-left:var(--spectrum-listitem-texticon-icon-gap)}:host([dir=rtl]) .checkmark{margin-right:var(--spectrum-listitem-texticon-icon-gap)}.checkmark{flex-grow:0;margin-top:calc(var(--spectrum-listitem-texticon-ui-icon-margin-top) - var(--spectrum-listitem-texticon-padding-y) + 1px)} | ||
`; | ||
export default styles; | ||
//# sourceMappingURL=spectrum-checkmark.css.js.map |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -14,5 +14,5 @@ /* | ||
const styles = css ` | ||
:host([dir=ltr]) .chevron{margin-left:var(--spectrum-listitem-texticon-icon-gap)}:host([dir=rtl]) .chevron{margin-right:var(--spectrum-listitem-texticon-icon-gap)}.chevron{flex-grow:0;margin-top:var(--spectrum-listitem-texticon-icon-margin-top)}:host([dir=rtl]) .chevron{transform:matrix(-1,0,0,1,0,0)} | ||
:host([dir=ltr]) .chevron{margin-left:var(--spectrum-listitem-texticon-icon-gap)}:host([dir=rtl]) .chevron{margin-right:var(--spectrum-listitem-texticon-icon-gap)}.chevron{flex-grow:0;margin-top:calc(var(--spectrum-listitem-texticon-ui-icon-margin-top) - var(--spectrum-listitem-texticon-padding-y) + 1px)}:host([dir=rtl]) .chevron{transform:matrix(-1,0,0,1,0,0)} | ||
`; | ||
export default styles; | ||
//# sourceMappingURL=spectrum-chevron.css.js.map |
@@ -33,3 +33,3 @@ /* | ||
type: 'boolean', | ||
name: 'selectable', | ||
name: 'selects', | ||
}, | ||
@@ -59,3 +59,3 @@ ], | ||
replacement: | ||
':host([dir="ltr"][selectable]) ::slotted(sp-menu-item[selected])', | ||
':host([dir="ltr"][selects]) ::slotted(sp-menu-item[selected])', | ||
selector: | ||
@@ -66,3 +66,3 @@ '.spectrum-Menu[dir=ltr].is-selectable .spectrum-Menu-item.is-selected', | ||
replacement: | ||
':host([dir="rtl"][selectable]) ::slotted(sp-menu-item[selected])', | ||
':host([dir="rtl"][selects]) ::slotted(sp-menu-item[selected])', | ||
selector: | ||
@@ -69,0 +69,0 @@ '.spectrum-Menu[dir=rtl].is-selectable .spectrum-Menu-item.is-selected', |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -1,2 +0,2 @@ | ||
declare const styles: import("@spectrum-web-components/base").CSSResultGroup; | ||
declare const styles: import("@spectrum-web-components/base").CSSResult; | ||
export default styles; |
@@ -14,4 +14,4 @@ /* | ||
const styles = css ` | ||
:host{--spectrum-menu-margin-x:var(--spectrum-global-dimension-size-40);--spectrum-listitem-heading-text-size:var(--spectrum-global-dimension-font-size-50);--spectrum-listitem-heading-text-font-weight:400;--spectrum-listitem-heading-text-transform:uppercase;--spectrum-listitem-heading-letter-spacing:0.06em;--spectrum-listitem-heading-margin:var(--spectrum-global-dimension-size-75) 0 0 0;--spectrum-listitem-heading-padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150);--spectrum-listitem-padding-y:var(--spectrum-global-dimension-size-85);--spectrum-listitem-icon-margin-top:var(--spectrum-global-dimension-size-50);--spectrum-listitem-label-line-height:1.3;--spectrum-listitem-heading-line-height:var(--spectrum-alias-body-text-line-height,var(--spectrum-global-font-line-height-medium));--spectrum-listitem-divider-size:var(--spectrum-listitem-m-divider-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-divider-padding:var(--spectrum-listitem-m-divider-padding,3px);--spectrum-listitem-focus-indicator-size:var(--spectrum-listitem-m-focus-indicator-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-text-font-weight:var(--spectrum-listitem-m-text-font-weight,var(--spectrum-alias-body-text-font-weight));--spectrum-listitem-text-size:var(--spectrum-listitem-m-text-size,var(--spectrum-alias-item-text-size-m));--spectrum-listitem-height:var(--spectrum-listitem-m-height,var(--spectrum-alias-item-height-m));--spectrum-listitem-icon-gap:var(--spectrum-listitem-m-icon-gap,var(--spectrum-alias-item-workflow-icon-gap-m));--spectrum-listitem-padding-left:var(--spectrum-listitem-m-padding-left,var(--spectrum-alias-item-workflow-padding-left-m));--spectrum-listitem-padding-right:var(--spectrum-listitem-m-padding-right,var(--spectrum-alias-item-padding-m));--spectrum-listitem-thumbnail-padding-left:var(--spectrum-listitem-m-thumbnail-padding-left,var(--spectrum-alias-item-padding-m));display:inline-block;box-sizing:border-box;margin-top:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-bottom:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-left:0;margin-right:0;padding:0;list-style-type:none;overflow:auto}:host([dir=ltr][selectable]) ::slotted(sp-menu-item){padding-right:var(--spectrum-listitem-selectable-padding-right)}:host([dir=rtl][selectable]) ::slotted(sp-menu-item){padding-left:var(--spectrum-listitem-selectable-padding-right)}:host([dir=ltr][selectable]) ::slotted(sp-menu-item[selected]){padding-right:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}:host([dir=rtl][selectable]) ::slotted(sp-menu-item[selected]){padding-left:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
:host{--spectrum-menu-margin-x:var(--spectrum-global-dimension-size-40);--spectrum-listitem-heading-text-size:var(--spectrum-global-dimension-font-size-50);--spectrum-listitem-heading-text-font-weight:400;--spectrum-listitem-heading-text-transform:uppercase;--spectrum-listitem-heading-letter-spacing:0.06em;--spectrum-listitem-heading-margin:var(--spectrum-global-dimension-size-75) 0 0 0;--spectrum-listitem-heading-padding:0 var(--spectrum-global-dimension-size-450) 0 var(--spectrum-global-dimension-size-150);--spectrum-listitem-padding-y:var(--spectrum-global-dimension-size-85);--spectrum-listitem-icon-margin-top:var(--spectrum-global-dimension-size-50);--spectrum-listitem-label-line-height:1.3;--spectrum-listitem-heading-line-height:var(--spectrum-alias-body-text-line-height,var(--spectrum-global-font-line-height-medium));--spectrum-listitem-divider-size:var(--spectrum-listitem-m-divider-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-divider-padding:var(--spectrum-listitem-m-divider-padding,3px);--spectrum-listitem-focus-indicator-size:var(--spectrum-listitem-m-focus-indicator-size,var(--spectrum-alias-border-size-thick));--spectrum-listitem-text-font-weight:var(--spectrum-listitem-m-text-font-weight,var(--spectrum-alias-body-text-font-weight));--spectrum-listitem-text-size:var(--spectrum-listitem-m-text-size,var(--spectrum-alias-item-text-size-m));--spectrum-listitem-height:var(--spectrum-listitem-m-height,var(--spectrum-alias-item-height-m));--spectrum-listitem-icon-gap:var(--spectrum-listitem-m-icon-gap,var(--spectrum-alias-item-workflow-icon-gap-m));--spectrum-listitem-padding-left:var(--spectrum-listitem-m-padding-left,var(--spectrum-alias-item-workflow-padding-left-m));--spectrum-listitem-padding-right:var(--spectrum-listitem-m-padding-right,var(--spectrum-alias-item-padding-m));--spectrum-listitem-thumbnail-padding-left:var(--spectrum-listitem-m-thumbnail-padding-left,var(--spectrum-alias-item-padding-m));display:inline-block;box-sizing:border-box;margin-top:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-bottom:var(--spectrum-popover-padding-y,var(--spectrum-global-dimension-size-50));margin-left:0;margin-right:0;padding:0;list-style-type:none;overflow:auto}:host([dir=ltr][selects]) ::slotted(sp-menu-item){padding-right:var(--spectrum-listitem-selectable-padding-right)}:host([dir=rtl][selects]) ::slotted(sp-menu-item){padding-left:var(--spectrum-listitem-selectable-padding-right)}:host([dir=ltr][selects]) ::slotted(sp-menu-item[selected]){padding-right:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}:host([dir=rtl][selects]) ::slotted(sp-menu-item[selected]){padding-left:calc(var(--spectrum-listitem-padding-right) - var(--spectrum-popover-border-size, | ||
var(--spectrum-alias-border-size-thin)))}::slotted(sp-menu){display:block}:host{--spectrum-listheading-text-color:var(--spectrum-global-color-gray-700);background-color:var(--spectrum-listitem-m-background-color,var(--spectrum-alias-background-color-transparent))} | ||
@@ -18,0 +18,0 @@ `; |
@@ -54,3 +54,3 @@ /* | ||
</style> | ||
<sp-menu style="width: 150px;" selectable> | ||
<sp-menu style="width: 150px;" selects="single"> | ||
<sp-menu-item> | ||
@@ -57,0 +57,0 @@ Save |
@@ -50,2 +50,52 @@ /* | ||
}; | ||
export const singleSelect = () => { | ||
return html ` | ||
<sp-menu selects="single"> | ||
<sp-menu-item selected>Deselect</sp-menu-item> | ||
<sp-menu-item>Select Inverse</sp-menu-item> | ||
<sp-menu-item>Feather...</sp-menu-item> | ||
<sp-menu-item>Select and Mask...</sp-menu-item> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-item>Save Selection</sp-menu-item> | ||
<sp-menu-item disabled>Make Work Path</sp-menu-item> | ||
</sp-menu> | ||
<sp-popover open> | ||
<sp-menu selects="single"> | ||
<sp-menu-item>Deselect</sp-menu-item> | ||
<sp-menu-item>Select Inverse</sp-menu-item> | ||
<sp-menu-item selected>Feather...</sp-menu-item> | ||
<sp-menu-item>Select and Mask...</sp-menu-item> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-item>Save Selection</sp-menu-item> | ||
<sp-menu-item disabled>Make Work Path</sp-menu-item> | ||
</sp-menu> | ||
</sp-popover> | ||
`; | ||
}; | ||
export const multipleSelect = () => { | ||
return html ` | ||
<sp-menu selects="multiple"> | ||
<sp-menu-item>Deselect</sp-menu-item> | ||
<sp-menu-item>Select Inverse</sp-menu-item> | ||
<sp-menu-item selected>Feather...</sp-menu-item> | ||
<sp-menu-item>Select and Mask...</sp-menu-item> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-item selected>Save Selection</sp-menu-item> | ||
<sp-menu-item disabled>Make Work Path</sp-menu-item> | ||
</sp-menu> | ||
<sp-popover open> | ||
<sp-menu selects="multiple"> | ||
<sp-menu-item>Deselect</sp-menu-item> | ||
<sp-menu-item selected>Select Inverse</sp-menu-item> | ||
<sp-menu-item>Feather...</sp-menu-item> | ||
<sp-menu-item selected>Select and Mask...</sp-menu-item> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-item>Save Selection</sp-menu-item> | ||
<sp-menu-item disabled>Make Work Path</sp-menu-item> | ||
</sp-menu> | ||
</sp-popover> | ||
`; | ||
}; | ||
export const headersAndIcons = () => { | ||
@@ -95,3 +145,3 @@ return html ` | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-group> | ||
<sp-menu-group selects="single"> | ||
<span slot="header">Oakland</span> | ||
@@ -109,2 +159,39 @@ <sp-menu-item>City Center</sp-menu-item> | ||
}; | ||
export const MenuGroupSelects = () => { | ||
return html ` | ||
<sp-popover open style="width: 200px;"> | ||
<sp-menu selects="single"> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">One of these</span> | ||
<sp-menu-item>Camden</sp-menu-item> | ||
<sp-menu-item>Cedar Riverside</sp-menu-item> | ||
<sp-menu-item>Downtown</sp-menu-item> | ||
<sp-menu-item>Northeast Arts District</sp-menu-item> | ||
<sp-menu-item selected>Uptown</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">Or of these</span> | ||
<sp-menu-item>Lowertown</sp-menu-item> | ||
<sp-menu-item>Grand Ave</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group selects="multiple"> | ||
<span slot="header">Many of these</span> | ||
<sp-menu-item>Financial District</sp-menu-item> | ||
<sp-menu-item selected>South of Market</sp-menu-item> | ||
<sp-menu-item selected>North Beach</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-group selects="single"> | ||
<span slot="header">One of these</span> | ||
<sp-menu-item>City Center</sp-menu-item> | ||
<sp-menu-item disabled>Jack London Square</sp-menu-item> | ||
<sp-menu-item selected> | ||
My best friend's mom's house in the burbs just off | ||
Silverado street | ||
</sp-menu-item> | ||
</sp-menu-group> | ||
</sp-menu> | ||
</sp-popover> | ||
`; | ||
}; | ||
export const selectedOffPage = () => { | ||
@@ -125,2 +212,39 @@ return html ` | ||
}; | ||
export const MenuGroupSelectsMultiple = () => { | ||
return html ` | ||
<sp-popover open style="width: 200px;"> | ||
<sp-menu selects="multiple"> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">Many of these</span> | ||
<sp-menu-item>Camden</sp-menu-item> | ||
<sp-menu-item selected>Cedar Riverside</sp-menu-item> | ||
<sp-menu-item selected>Downtown</sp-menu-item> | ||
<sp-menu-item>Northeast Arts District</sp-menu-item> | ||
<sp-menu-item>Uptown</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">And these, too</span> | ||
<sp-menu-item>Lowertown</sp-menu-item> | ||
<sp-menu-item selected>Grand Ave</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group> | ||
<span slot="header">None of these</span> | ||
<sp-menu-item>Financial District</sp-menu-item> | ||
<sp-menu-item>South of Market</sp-menu-item> | ||
<sp-menu-item>North Beach</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-group selects="single"> | ||
<span slot="header">One of these</span> | ||
<sp-menu-item>City Center</sp-menu-item> | ||
<sp-menu-item disabled>Jack London Square</sp-menu-item> | ||
<sp-menu-item selected> | ||
My best friend's mom's house in the burbs just off | ||
Silverado street | ||
</sp-menu-item> | ||
</sp-menu-group> | ||
</sp-menu> | ||
</sp-popover> | ||
`; | ||
}; | ||
//# sourceMappingURL=menu.stories.js.map |
@@ -15,8 +15,12 @@ /* | ||
import '../sp-menu-item.js'; | ||
import { fixture, elementUpdated, html, expect } from '@open-wc/testing'; | ||
import '../sp-menu-divider.js'; | ||
import { fixture, elementUpdated, html, expect, waitUntil, oneEvent, } from '@open-wc/testing'; | ||
const managedItems = (menu) => { | ||
return menu.childItems.filter((item) => item.menuData.selectionRoot === menu); | ||
}; | ||
describe('Menu group', () => { | ||
it('renders', async () => { | ||
const el = await fixture(html ` | ||
<sp-menu> | ||
<sp-menu-group> | ||
<sp-menu selects="single"> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">Section Heading</span> | ||
@@ -28,3 +32,3 @@ <sp-menu-item>Action 1</sp-menu-item> | ||
<sp-menu-divider></sp-menu-divider> | ||
<sp-menu-group> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">Section Heading</span> | ||
@@ -36,6 +40,190 @@ <sp-menu-item>Save</sp-menu-item> | ||
`); | ||
await waitUntil(() => { | ||
return managedItems(el).length === 5; | ||
}, `expected menu group to manage 5 children, received ${managedItems(el).length} of ${el.childItems.length}`); | ||
await elementUpdated(el); | ||
await expect(el).to.be.accessible(); | ||
}); | ||
it('manages [slot="header"] content', async () => { | ||
const el = await fixture(html ` | ||
<sp-menu-group></sp-menu-group> | ||
`); | ||
await elementUpdated(el); | ||
const slot = el.shadowRoot.querySelector('[name="header"'); | ||
const header = document.createElement('span'); | ||
header.textContent = 'Header'; | ||
header.slot = 'header'; | ||
expect(header.id).to.equal(''); | ||
let slotchanged = oneEvent(slot, 'slotchange'); | ||
el.append(header); | ||
await slotchanged; | ||
expect(header.id).to.equal(el.headerId); | ||
slotchanged = oneEvent(slot, 'slotchange'); | ||
header.remove(); | ||
await slotchanged; | ||
expect(header.id).to.equal(''); | ||
}); | ||
it('handles selects for nested menu groups', async () => { | ||
const el = await fixture(html ` | ||
<sp-menu selects="single"> | ||
<sp-menu-item selected>First</sp-menu-item> | ||
<sp-menu-item>Second</sp-menu-item> | ||
<sp-menu-group id="mg-multi" selects="multiple"> | ||
<sp-menu-item selected>Multi1</sp-menu-item> | ||
<sp-menu-item>Multi2</sp-menu-item> | ||
<sp-menu-group id="mg-sub-inherit" selects="inherit"> | ||
<sp-menu-item>SubInherit1</sp-menu-item> | ||
<sp-menu-item>SubInherit2</sp-menu-item> | ||
</sp-menu-group> | ||
</sp-menu-group> | ||
<sp-menu-group id="mg-single" selects="single"> | ||
<sp-menu-item selected>Single1</sp-menu-item> | ||
<sp-menu-item>Single2</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group id="mg-none"> | ||
<sp-menu-item>Inherit1</sp-menu-item> | ||
<sp-menu-item>Inherit2</sp-menu-item> | ||
</sp-menu-group> | ||
<sp-menu-group id="mg-inherit" selects="inherit"> | ||
<sp-menu-item>Inherit1</sp-menu-item> | ||
<sp-menu-item>Inherit2</sp-menu-item> | ||
</sp-menu-group> | ||
</sp-menu> | ||
`); | ||
await waitUntil(() => managedItems(el).length === 4, `expected outer menu to manage 4 items (2 are inherited), got ${managedItems(el).length}, with ${el.childItems.length} total`); | ||
await waitUntil(() => el.selectedItems.length === 1, 'expected 1 selected item'); | ||
await elementUpdated(el); | ||
const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const secondItem = el.querySelector('sp-menu-item:nth-of-type(2)'); | ||
const multiGroup = el.querySelector('sp-menu-group#mg-multi'); | ||
const multiItem1 = multiGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const multiItem2 = multiGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
await waitUntil(() => managedItems(multiGroup).length === 4, `selects="#mg-multi should manage 4 items (2 are inherited), received ${managedItems(multiGroup).length}`); | ||
const singleGroup = el.querySelector('sp-menu-group#mg-single'); | ||
const singleItem1 = singleGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const singleItem2 = singleGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
await waitUntil(() => managedItems(singleGroup).length === 2, 'selects="#mg-none should manage 4 items (2 are inherited)'); | ||
const noneGroup = el.querySelector('sp-menu-group#mg-none'); | ||
const noneItem1 = noneGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const noneItem2 = noneGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
await waitUntil(() => managedItems(noneGroup).length === 2, `selects="#mg-none" should manage 2 items, received ${managedItems(noneGroup).length}`); | ||
const inheritGroup = el.querySelector('sp-menu-group#mg-inherit'); | ||
const inheritItem1 = inheritGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const inheritItem2 = inheritGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
expect(firstItem.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(secondItem.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(multiItem1.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(multiItem2.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(singleItem1.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(singleItem2.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(noneItem1.getAttribute('role')).to.equal('menuitem'); | ||
expect(noneItem2.getAttribute('role')).to.equal('menuitem'); | ||
expect(inheritItem1.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(inheritItem2.getAttribute('role')).to.equal('menuitemradio'); | ||
await elementUpdated(firstItem); | ||
expect(singleItem1.selected).to.be.true; | ||
expect(firstItem.selected).to.be.true; | ||
expect(secondItem.selected, 'second item not selected').to.be.false; | ||
expect(el.value).to.equal('First'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('false'); | ||
secondItem.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(firstItem); | ||
await elementUpdated(secondItem); | ||
expect(firstItem.selected, 'first item not selected').to.be.false; | ||
expect(secondItem.selected).to.be.true; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('Second'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
inheritItem1.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(inheritItem1); | ||
await elementUpdated(secondItem); | ||
expect(secondItem.selected, 'second item not selected again').to.be | ||
.false; | ||
expect(inheritItem1.selected).to.be.true; | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(inheritItem1.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('Inherit1'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
noneItem2.click(); | ||
await elementUpdated(noneGroup); | ||
await elementUpdated(noneItem2); | ||
expect(inheritItem1.selected).to.be.true; | ||
expect(noneItem2.selected, 'none item not selected').to.be.false; | ||
expect(el.value).to.equal('Inherit1'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
singleItem2.click(); | ||
await elementUpdated(singleGroup); | ||
await elementUpdated(singleItem1); | ||
await elementUpdated(singleItem2); | ||
expect(singleItem1.selected, 'first item not selected').to.be.false; | ||
expect(singleItem2.selected).to.be.true; | ||
expect(inheritItem1.selected).to.be.true; | ||
expect(singleItem1.getAttribute('aria-checked')).to.equal('false'); | ||
expect(singleItem2.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('Inherit1'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
//expect(singleGroup.value).to.equal('Inherit1') | ||
expect(singleGroup.selectedItems.length).to.equal(1); | ||
multiItem2.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(multiItem2); | ||
expect(multiItem1.selected).to.be.true; | ||
expect(multiItem2.selected).to.be.true; | ||
expect(inheritItem1.selected).to.be.true; | ||
expect(multiItem1.getAttribute('aria-checked')).to.equal('true'); | ||
expect(multiItem2.getAttribute('aria-checked')).to.equal('true'); | ||
//expect(multiGroup.value).to.equal('Inherit1') | ||
expect(multiGroup.selectedItems.length).to.equal(2); | ||
}); | ||
it('handles changing managed items for selects changes', async () => { | ||
const el = await fixture(html ` | ||
<sp-menu selects="multiple" value-separator="--"> | ||
<sp-menu-item selected>First</sp-menu-item> | ||
<sp-menu-item>Second</sp-menu-item> | ||
<sp-menu-group id="mg-inherit" selects="inherit"> | ||
<sp-menu-item>Inherit1</sp-menu-item> | ||
<sp-menu-item>Inherit2</sp-menu-item> | ||
<sp-menu-group id="mg-sub-inherit" selects="inherit"> | ||
<sp-menu-item>SubInherit1</sp-menu-item> | ||
<sp-menu-item selected>SubInherit2</sp-menu-item> | ||
</sp-menu-group> | ||
</sp-menu-group> | ||
</sp-menu> | ||
`); | ||
await waitUntil(() => managedItems(el).length == 6, `expected outer menu to manage 6 items, manages ${managedItems(el).length}`); | ||
await waitUntil(() => el.selectedItems.length == 2, 'expected 2 selected item'); | ||
await elementUpdated(el); | ||
const inheritGroup = el.querySelector('sp-menu-group#mg-inherit'); | ||
const inheritItem1 = inheritGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const inheritItem2 = inheritGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
const subInheritGroup = el.querySelector('sp-menu-group#mg-sub-inherit'); | ||
const subInheritItem1 = subInheritGroup.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const subInheritItem2 = subInheritGroup.querySelector('sp-menu-item:nth-of-type(2)'); | ||
expect(inheritItem1.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(inheritItem2.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(subInheritItem1.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(subInheritItem2.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(el.value).to.equal('First--SubInherit2'); | ||
expect(el.selectedItems.length).to.equal(2); | ||
inheritGroup.setAttribute('selects', 'single'); | ||
await elementUpdated(inheritGroup); | ||
await elementUpdated(el); | ||
await waitUntil(() => { | ||
return managedItems(inheritGroup).length === 4; | ||
}, `expected new single sub-group to manage 4 items, received ${managedItems(inheritGroup).length} because "selects === ${inheritGroup.selects}`); | ||
await waitUntil(() => managedItems(el).length === 2, `expected outer menu to manage 2 items with none inherited, received ${managedItems(el).length}`); | ||
expect(inheritGroup.value).to.equal('SubInherit2'); | ||
expect(inheritGroup.selectedItems.length).to.equal(1); | ||
expect(el.value).to.equal('First'); | ||
expect(inheritItem1.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(inheritItem2.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(subInheritItem1.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(subInheritItem2.getAttribute('role')).to.equal('menuitemradio'); | ||
}); | ||
}); | ||
//# sourceMappingURL=menu-group.test.js.map |
@@ -15,3 +15,3 @@ /* | ||
import '@spectrum-web-components/menu'; | ||
import { fixture, elementUpdated, html, expect } from '@open-wc/testing'; | ||
import { fixture, elementUpdated, html, expect, waitUntil, } from '@open-wc/testing'; | ||
describe('Menu item', () => { | ||
@@ -21,7 +21,6 @@ it('renders', async () => { | ||
<sp-menu> | ||
<sp-menu-item selected> | ||
Selected | ||
</sp-menu-item> | ||
<sp-menu-item selected>Selected</sp-menu-item> | ||
</sp-menu> | ||
`); | ||
await waitUntil(() => el.childItems.length == 1, 'expected menu group to manage 1 child'); | ||
await elementUpdated(el); | ||
@@ -41,5 +40,3 @@ await expect(el).to.be.accessible(); | ||
const el = await fixture(html ` | ||
<sp-menu-item selected> | ||
Selected Text | ||
</sp-menu-item> | ||
<sp-menu-item selected>Selected Text</sp-menu-item> | ||
`); | ||
@@ -51,5 +48,3 @@ expect(el.itemText).to.equal('Selected Text'); | ||
const el = await fixture(html ` | ||
<sp-menu-item selected> | ||
Selected Text | ||
</sp-menu-item> | ||
<sp-menu-item selected>Selected Text</sp-menu-item> | ||
`); | ||
@@ -56,0 +51,0 @@ expect(el.itemText).to.equal('Selected Text'); |
@@ -17,2 +17,3 @@ /* | ||
import { executeServerCommand, sendKeys } from '@web/test-runner-commands'; | ||
import { spy } from 'sinon'; | ||
describe('Menu [selects]', () => { | ||
@@ -403,2 +404,20 @@ let el; | ||
}); | ||
it('can have them `preventDefault()`ed', async () => { | ||
const preventSpy = spy(); | ||
expect(groupA.value).to.equal(''); | ||
expect(groupB.value).to.equal(''); | ||
const item1a = options[0]; | ||
const item1b = options[3]; | ||
groupA.addEventListener('change', (event) => { | ||
event.preventDefault(); | ||
preventSpy(); | ||
}); | ||
const change = oneEvent(el, 'change'); | ||
item1a.click(); | ||
item1b.click(); | ||
await change; | ||
expect(preventSpy.callCount).to.equal(1); | ||
expect(groupA.value).to.equal(''); | ||
expect(groupB.value).to.equal('1b'); | ||
}); | ||
}); | ||
@@ -502,3 +521,5 @@ it('manages a single selection when [selects="single"]', async () => { | ||
it('manages focus', async () => { | ||
el.focus(); | ||
await elementUpdated(groupA); | ||
await elementUpdated(groupB); | ||
options[0].focus(); | ||
await elementUpdated(el); | ||
@@ -512,2 +533,4 @@ await sendKeys({ press: 'ArrowDown' }); | ||
expect(option.focused, 'option visually focused').to.be.true; | ||
await sendKeys({ press: 'Space' }); | ||
expect(parentElement.value).to.equal(option.value); | ||
await sendKeys({ press: 'ArrowDown' }); | ||
@@ -514,0 +537,0 @@ } |
@@ -18,2 +18,3 @@ /* | ||
import { spy } from 'sinon'; | ||
import { sendKeys } from '@web/test-runner-commands'; | ||
describe('Menu', () => { | ||
@@ -31,2 +32,3 @@ it('renders empty', async () => { | ||
.false; | ||
expect(el.getAttribute('role')).to.equal('menu'); | ||
el.focus(); | ||
@@ -85,3 +87,3 @@ await elementUpdated(el); | ||
const el = await fixture(html ` | ||
<sp-menu> | ||
<sp-menu label="Pick an action:"> | ||
<sp-menu-item>Deselect</sp-menu-item> | ||
@@ -96,2 +98,3 @@ <sp-menu-item>Select Inverse</sp-menu-item> | ||
`); | ||
await waitUntil(() => el.childItems.length == 6, 'expected menu to manage 6 menu items'); | ||
await elementUpdated(el); | ||
@@ -104,3 +107,3 @@ const inTabindexElement = el.querySelector('[tabindex]:not([tabindex="-1"])'); | ||
const el = await fixture(html ` | ||
<sp-menu> | ||
<sp-menu selects="single"> | ||
<sp-menu-item>Not Selected</sp-menu-item> | ||
@@ -124,4 +127,6 @@ <sp-menu-item selected>Selected</sp-menu-item> | ||
`); | ||
await waitUntil(() => el.childItems.length == 3, 'expected menu to manage 3 items'); | ||
await elementUpdated(el); | ||
await expect(el).to.be.accessible(); | ||
expect(el.getAttribute('role')).to.equal('menu'); | ||
}); | ||
@@ -140,2 +145,3 @@ it('handle focus and keyboard input', async () => { | ||
`); | ||
await waitUntil(() => el.childItems.length == 6, 'expected menu to manage 6 items'); | ||
await elementUpdated(el); | ||
@@ -146,2 +152,6 @@ const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
el.focus(); | ||
await elementUpdated(el); | ||
// Activate :focus-visible | ||
await sendKeys({ press: 'ArrowDown' }); | ||
await sendKeys({ press: 'ArrowUp' }); | ||
expect(document.activeElement === el).to.be.true; | ||
@@ -161,3 +171,3 @@ expect(firstItem.focused).to.be.true; | ||
<sp-menu> | ||
<sp-menu-group> | ||
<sp-menu-group selects="inherit"> | ||
<span slot="header">Options</span> | ||
@@ -168,16 +178,25 @@ <sp-menu-item>Deselect</sp-menu-item> | ||
`); | ||
await waitUntil(() => el.childItems.length == 1, 'expected menu to manage 1 item'); | ||
await elementUpdated(el); | ||
const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
el.focus(); | ||
expect(document.activeElement === el).to.be.true; | ||
expect(firstItem.focused).to.be.true; | ||
await elementUpdated(el); | ||
// Activate :focus-visible | ||
await sendKeys({ press: 'ArrowDown' }); | ||
await sendKeys({ press: 'ArrowUp' }); | ||
expect(document.activeElement === el, 'active element').to.be.true; | ||
expect(firstItem.focused, 'visually focused').to.be.true; | ||
el.blur(); | ||
const group = el.querySelector('sp-menu-group'); | ||
const prependedItem = document.createElement('sp-menu-item'); | ||
prependedItem.innerHTML = 'Prepended Item'; | ||
prependedItem.textContent = 'Prepended Item'; | ||
const appendedItem = document.createElement('sp-menu-item'); | ||
prependedItem.innerHTML = 'Appended Item'; | ||
appendedItem.textContent = 'Appended Item'; | ||
group.prepend(prependedItem); | ||
group.append(appendedItem); | ||
await elementUpdated(el); | ||
await waitUntil(() => { | ||
return el.childItems.length == 3; | ||
}, 'expected menu to manage 3 items'); | ||
await elementUpdated(el); | ||
expect(document.activeElement === el).to.be.false; | ||
@@ -187,7 +206,11 @@ expect(firstItem.focused).to.be.false; | ||
el.focus(); | ||
expect(document.activeElement === el).to.be.true; | ||
expect(prependedItem.focused).to.be.true; | ||
// Activate :focus-visible | ||
await sendKeys({ press: 'ArrowDown' }); | ||
await sendKeys({ press: 'ArrowUp' }); | ||
expect(document.activeElement === el, 'another active element').to.be | ||
.true; | ||
expect(prependedItem.focused, 'another visibly focused').to.be.true; | ||
el.dispatchEvent(arrowUpEvent); | ||
expect(document.activeElement === el).to.be.true; | ||
expect(appendedItem.focused).to.be.true; | ||
expect(document.activeElement === el, 'last active element').to.be.true; | ||
expect(appendedItem.focused, 'last visibly focused').to.be.true; | ||
}); | ||
@@ -202,2 +225,3 @@ it('cleans up when tabbing away', async () => { | ||
`); | ||
await waitUntil(() => el.childItems.length == 3, 'expected menu to manage 3 items'); | ||
await elementUpdated(el); | ||
@@ -208,2 +232,5 @@ const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
el.focus(); | ||
// Activate :focus-visible | ||
await sendKeys({ press: 'ArrowDown' }); | ||
await sendKeys({ press: 'ArrowUp' }); | ||
expect(document.activeElement === el).to.be.true; | ||
@@ -250,3 +277,77 @@ expect(firstItem.focused, 'first').to.be.true; | ||
}); | ||
it('handles single selection', async () => { | ||
const el = await fixture(html ` | ||
<sp-menu selects="single"> | ||
<sp-menu-item selected>First</sp-menu-item> | ||
<sp-menu-item>Second</sp-menu-item> | ||
<sp-menu-item>Third</sp-menu-item> | ||
</sp-menu> | ||
`); | ||
await waitUntil(() => el.childItems.length == 3, 'expected menu to manage 3 items'); | ||
await waitUntil(() => el.selectedItems.length == 1, 'expected menu to have 1 selected item'); | ||
await elementUpdated(el); | ||
const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const secondItem = el.querySelector('sp-menu-item:nth-of-type(2)'); | ||
expect(firstItem.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(secondItem.getAttribute('role')).to.equal('menuitemradio'); | ||
expect(firstItem.selected).to.be.true; | ||
expect(secondItem.selected).to.be.false; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(el.value).to.equal('First'); | ||
secondItem.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(firstItem); | ||
await elementUpdated(secondItem); | ||
expect(firstItem.selected).to.be.false; | ||
expect(secondItem.selected).to.be.true; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('Second'); | ||
}); | ||
it('handles multiple selection', async () => { | ||
const changeSpy = spy(); | ||
const el = await fixture(html ` | ||
<sp-menu selects="multiple" @change=${() => changeSpy()}> | ||
<sp-menu-item selected>First</sp-menu-item> | ||
<sp-menu-item>Second</sp-menu-item> | ||
<sp-menu-item>Third</sp-menu-item> | ||
</sp-menu> | ||
`); | ||
await waitUntil(() => el.childItems.length == 3, 'expected menu to manage 3 items'); | ||
await elementUpdated(el); | ||
const firstItem = el.querySelector('sp-menu-item:nth-of-type(1)'); | ||
const secondItem = el.querySelector('sp-menu-item:nth-of-type(2)'); | ||
expect(firstItem.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(secondItem.getAttribute('role')).to.equal('menuitemcheckbox'); | ||
expect(firstItem.selected).to.be.true; | ||
expect(secondItem.selected).to.be.false; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(el.value).to.equal('First'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
secondItem.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(firstItem); | ||
await elementUpdated(secondItem); | ||
expect(changeSpy.callCount, 'one change').to.equal(1); | ||
expect(firstItem.selected).to.be.true; | ||
expect(secondItem.selected).to.be.true; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('First,Second'); | ||
expect(el.selectedItems.length).to.equal(2); | ||
firstItem.click(); | ||
await elementUpdated(el); | ||
await elementUpdated(firstItem); | ||
await elementUpdated(secondItem); | ||
expect(changeSpy.callCount, 'two changes').to.equal(2); | ||
expect(firstItem.selected).to.be.false; | ||
expect(secondItem.selected).to.be.true; | ||
expect(firstItem.getAttribute('aria-checked')).to.equal('false'); | ||
expect(secondItem.getAttribute('aria-checked')).to.equal('true'); | ||
expect(el.value).to.equal('Second'); | ||
expect(el.selectedItems.length).to.equal(1); | ||
}); | ||
}); | ||
//# sourceMappingURL=menu.test.js.map |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
464393
4999
24
5150
163
0