-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Pull Request ## 🤨 Rationale - #791 This PR only introduces groups to the `Select`. Providing this feature for the `Combobox` will be handled separately.
- Loading branch information
1 parent
e5a6a07
commit 9ed4ff0
Showing
21 changed files
with
1,728 additions
and
119 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@ni-nimble-components-278f4b89-b1f8-402d-a3e7-ba0338b1b348.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "List option groups for Select", | ||
"packageName": "@ni/nimble-components", | ||
"email": "26874831+atmgrifter00@users.noreply.github.com", | ||
"dependentChangeType": "patch" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
packages/nimble-components/src/list-option-group/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation'; | ||
import { | ||
observable, | ||
attr, | ||
volatile, | ||
Observable | ||
} from '@microsoft/fast-element'; | ||
import { styles } from './styles'; | ||
import { template } from './template'; | ||
import { ListOption } from '../list-option'; | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'nimble-list-option-group': ListOptionGroup; | ||
} | ||
} | ||
|
||
/** | ||
* A nimble-styled HTML listbox option group | ||
*/ | ||
export class ListOptionGroup extends FoundationElement { | ||
/** | ||
* The label for the group. | ||
* | ||
* @public | ||
* @remarks | ||
* If a label is also provided via slotted content, the label attribute | ||
* will have precedence. | ||
*/ | ||
@attr | ||
public label?: string; | ||
|
||
/** | ||
* The hidden state of the element. | ||
* | ||
* @public | ||
* @defaultValue - false | ||
* @remarks | ||
* HTML Attribute: hidden | ||
*/ | ||
@attr({ mode: 'boolean' }) | ||
public override hidden = false; | ||
|
||
/** | ||
* @internal | ||
* This attribute is required to allow use-cases that offer dynamic filtering | ||
* (like the Select) to visually hide groups that are filtered out, but still | ||
* allow users to use the native 'hidden' attribute without it being affected | ||
* by the filtering process. | ||
*/ | ||
@attr({ attribute: 'visually-hidden', mode: 'boolean' }) | ||
public visuallyHidden = false; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
@attr({ attribute: 'top-separator-visible', mode: 'boolean' }) | ||
public topSeparatorVisible = false; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
@attr({ attribute: 'bottom-separator-visible', mode: 'boolean' }) | ||
public bottomSeparatorVisible = false; | ||
|
||
/** @internal */ | ||
@observable | ||
public hasOverflow = false; | ||
|
||
/** @internal */ | ||
public labelSlot!: HTMLSlotElement; | ||
|
||
/** @internal */ | ||
@observable | ||
public listOptions!: ListOption[]; | ||
|
||
/** @internal */ | ||
@volatile | ||
public get labelContent(): string { | ||
if (this.label) { | ||
return this.label; | ||
} | ||
|
||
if (!this.$fastController.isConnected) { | ||
return ''; | ||
} | ||
|
||
const nodes = this.labelSlot.assignedNodes(); | ||
return nodes | ||
.filter(node => node.textContent?.trim() !== '') | ||
.map(node => node.textContent?.trim()) | ||
.join(' '); | ||
} | ||
|
||
private readonly hiddenOptions: Set<ListOption> = new Set(); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public clickHandler(e: MouseEvent): void { | ||
e.preventDefault(); | ||
e.stopImmediatePropagation(); | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public handleChange(source: unknown, propertyName: string): void { | ||
if ( | ||
source instanceof ListOption | ||
&& (propertyName === 'hidden' || propertyName === 'visuallyHidden') | ||
) { | ||
if (source.hidden || source.visuallyHidden) { | ||
this.hiddenOptions.add(source); | ||
} else { | ||
this.hiddenOptions.delete(source); | ||
} | ||
|
||
this.visuallyHidden = this.hiddenOptions.size === this.listOptions.length; | ||
} | ||
} | ||
|
||
private listOptionsChanged( | ||
prev: ListOption[] | undefined, | ||
next: ListOption[] | ||
): void { | ||
this.hiddenOptions.clear(); | ||
next.filter(o => o.hidden || o.visuallyHidden).forEach(o => this.hiddenOptions.add(o)); | ||
prev?.forEach(o => { | ||
const notifier = Observable.getNotifier(o); | ||
notifier.unsubscribe(this, 'hidden'); | ||
notifier.unsubscribe(this, 'visuallyHidden'); | ||
}); | ||
|
||
let allOptionsHidden = true; | ||
next?.forEach(o => { | ||
const notifier = Observable.getNotifier(o); | ||
notifier.subscribe(this, 'hidden'); | ||
notifier.subscribe(this, 'visuallyHidden'); | ||
allOptionsHidden = allOptionsHidden && (o.hidden || o.visuallyHidden); | ||
}); | ||
|
||
this.visuallyHidden = next.length === 0 || allOptionsHidden; | ||
} | ||
} | ||
|
||
const nimbleListOptionGroup = ListOptionGroup.compose({ | ||
baseName: 'list-option-group', | ||
baseClass: FoundationElement, | ||
template, | ||
styles | ||
}); | ||
|
||
DesignSystem.getOrCreate() | ||
.withPrefix('nimble') | ||
.register(nimbleListOptionGroup()); | ||
export const listOptionGroupTag = 'nimble-list-option-group'; |
72 changes: 72 additions & 0 deletions
72
packages/nimble-components/src/list-option-group/styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { css } from '@microsoft/fast-element'; | ||
import { display } from '../utilities/style/display'; | ||
|
||
import { | ||
borderColor, | ||
fillHoverColor, | ||
fillHoverSelectedColor, | ||
fillSelectedColor, | ||
groupHeaderFont, | ||
groupHeaderFontColor, | ||
groupHeaderTextTransform, | ||
smallPadding | ||
} from '../theme-provider/design-tokens'; | ||
|
||
export const styles = css` | ||
${display('flex')} | ||
:host { | ||
cursor: default; | ||
flex-direction: column; | ||
} | ||
:host([visually-hidden]) { | ||
display: none; | ||
} | ||
:host::after, | ||
:host::before { | ||
content: ' '; | ||
margin-top: ${smallPadding}; | ||
margin-bottom: ${smallPadding}; | ||
border-bottom: ${borderColor} 2px solid; | ||
opacity: 0.1; | ||
display: none; | ||
} | ||
:host([top-separator-visible])::before, | ||
:host([bottom-separator-visible])::after { | ||
display: block; | ||
} | ||
slot[name='option']::slotted([role='option']) { | ||
background-color: transparent; | ||
} | ||
slot[name='option']::slotted([role='option']:hover) { | ||
background-color: ${fillHoverColor}; | ||
} | ||
slot[name='option']::slotted([role='option'][active-option]) { | ||
background-color: ${fillSelectedColor}; | ||
} | ||
slot[name='option']::slotted([role='option'][active-option]:hover) { | ||
background-color: ${fillHoverSelectedColor}; | ||
} | ||
.label-display { | ||
font: ${groupHeaderFont}; | ||
text-transform: ${groupHeaderTextTransform}; | ||
color: ${groupHeaderFontColor}; | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
margin-left: ${smallPadding}; | ||
margin-bottom: ${smallPadding}; | ||
} | ||
.label-slot.hidden { | ||
display: none; | ||
} | ||
`; |
39 changes: 39 additions & 0 deletions
39
packages/nimble-components/src/list-option-group/template.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { html, ref, slotted, when } from '@microsoft/fast-element'; | ||
import type { ListOptionGroup } from '.'; | ||
import { overflow } from '../utilities/directive/overflow'; | ||
import { ListOption } from '../list-option'; | ||
|
||
const isListOption = (n: Node): boolean => { | ||
return n instanceof ListOption; | ||
}; | ||
|
||
// prettier-ignore | ||
export const template = html<ListOptionGroup>` | ||
<template | ||
role="group" | ||
aria-label="${x => x.labelContent}" | ||
slot="option" | ||
> | ||
<span ${overflow('hasOverflow')} | ||
class="label-display" | ||
aria-hidden="true" | ||
title=${x => (x.hasOverflow && x.labelContent ? x.labelContent : null)} | ||
@click="${(x, c) => x.clickHandler(c.event as MouseEvent)}" | ||
> | ||
${when(x => (typeof x.label === 'string'), html<ListOptionGroup>`${x => x.label}`)} | ||
<slot ${ref('labelSlot')} | ||
class="label-slot ${x => (typeof x.label === 'string' ? 'hidden' : '')}" | ||
> | ||
</slot> | ||
</span> | ||
<span class="content" part="content" role="none"> | ||
<slot name="option" | ||
${slotted({ | ||
flatten: true, | ||
filter: (n: Node) => isListOption(n), | ||
property: 'listOptions' | ||
})} | ||
></slot> | ||
</span> | ||
</template> | ||
`; |
Oops, something went wrong.