-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Refactor toggle directive into popover component (#983)
* refactor(dropdown): Migrated to popover component * extracted a base option-like class for dropdown item and select item * utility classes refactoring * refactor(select): Use the underlying popover * Improved behavior to mimic the native select * WAI-ARIA improvements * refactor(dropdown): Detached target behavior * refactor: Moved keybindings to handleEvent pattern * chore: Improved storybook samples * refactor: RootClickControllerConfig as type Co-authored-by: Simeon Simeonoff <sim.simeonoff@gmail.com>
- Loading branch information
1 parent
56a37af
commit 3a4d52e
Showing
26 changed files
with
3,982 additions
and
2,585 deletions.
There are no files selected for viewing
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,5 @@ | ||
import { addons } from '@storybook/manager-api'; | ||
|
||
addons.setConfig({ | ||
enableShortcuts: false, | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import type { ReactiveController, ReactiveControllerHost } from 'lit'; | ||
|
||
type RootClickControllerConfig = { | ||
hideCallback?: Function; | ||
target?: HTMLElement; | ||
}; | ||
|
||
type RootClickControllerHost = ReactiveControllerHost & | ||
HTMLElement & { | ||
open: boolean; | ||
keepOpenOnOutsideClick: boolean; | ||
hide(): void; | ||
}; | ||
|
||
class RootClickController implements ReactiveController { | ||
constructor( | ||
private readonly host: RootClickControllerHost, | ||
private config?: RootClickControllerConfig | ||
) { | ||
this.host.addController(this); | ||
} | ||
|
||
private addEventListeners() { | ||
if (!this.host.keepOpenOnOutsideClick) { | ||
document.addEventListener('click', this); | ||
} | ||
} | ||
|
||
private removeEventListeners() { | ||
document.removeEventListener('click', this); | ||
} | ||
|
||
private configureListeners() { | ||
this.host.open ? this.addEventListeners() : this.removeEventListeners(); | ||
} | ||
|
||
public handleEvent(event: MouseEvent) { | ||
if (this.host.keepOpenOnOutsideClick) { | ||
return; | ||
} | ||
|
||
const path = event.composed ? event.composedPath() : [event.target]; | ||
const target = this.config?.target || null; | ||
if (path.includes(this.host) || path.includes(target)) { | ||
return; | ||
} | ||
|
||
this.hide(); | ||
} | ||
|
||
private hide() { | ||
this.config?.hideCallback | ||
? this.config.hideCallback.call(this.host) | ||
: this.host.hide(); | ||
} | ||
|
||
public update(config?: RootClickControllerConfig) { | ||
if (config) { | ||
this.config = { ...this.config, ...config }; | ||
} | ||
this.configureListeners(); | ||
} | ||
|
||
public hostConnected() { | ||
this.configureListeners(); | ||
} | ||
|
||
public hostDisconnected() { | ||
this.removeEventListeners(); | ||
} | ||
} | ||
|
||
export function addRootClickHandler( | ||
host: RootClickControllerHost, | ||
config?: RootClickControllerConfig | ||
) { | ||
return new RootClickController(host, config); | ||
} |
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,98 @@ | ||
import type { ReactiveController, ReactiveControllerHost } from 'lit'; | ||
|
||
type RootScrollControllerConfig = { | ||
hideCallback?: Function; | ||
resetListeners?: boolean; | ||
}; | ||
|
||
type RootScrollControllerHost = ReactiveControllerHost & { | ||
open: boolean; | ||
hide(): void; | ||
scrollStrategy?: 'scroll' | 'close' | 'block'; | ||
}; | ||
|
||
type ScrollRecord = { scrollTop: number; scrollLeft: number }; | ||
|
||
class RootScrollController implements ReactiveController { | ||
private _cache: WeakMap<Element, ScrollRecord>; | ||
|
||
constructor( | ||
private readonly host: RootScrollControllerHost, | ||
private config?: RootScrollControllerConfig | ||
) { | ||
this._cache = new WeakMap(); | ||
this.host.addController(this); | ||
} | ||
|
||
private configureListeners() { | ||
this.host.open ? this.addEventListeners() : this.removeEventListeners(); | ||
} | ||
|
||
private hide() { | ||
this.config?.hideCallback | ||
? this.config.hideCallback.call(this.host) | ||
: this.host.hide(); | ||
} | ||
|
||
private addEventListeners() { | ||
if (this.host.scrollStrategy !== 'scroll') { | ||
document.addEventListener('scroll', this, { capture: true }); | ||
} | ||
} | ||
|
||
private removeEventListeners() { | ||
document.removeEventListener('scroll', this, { capture: true }); | ||
this._cache = new WeakMap(); | ||
} | ||
|
||
public handleEvent(event: Event) { | ||
this.host.scrollStrategy === 'close' ? this.hide() : this._block(event); | ||
} | ||
|
||
private _block(event: Event) { | ||
event.preventDefault(); | ||
const element = event.target as Element; | ||
const cache = this._cache; | ||
|
||
if (!cache.has(element)) { | ||
cache.set(element, { | ||
scrollTop: element.firstElementChild?.scrollTop ?? element.scrollTop, | ||
scrollLeft: element.firstElementChild?.scrollLeft ?? element.scrollLeft, | ||
}); | ||
} | ||
|
||
const record = cache.get(element)!; | ||
Object.assign(element, record); | ||
|
||
if (element.firstElementChild) { | ||
Object.assign(element.firstElementChild, record); | ||
} | ||
} | ||
|
||
public update(config?: RootScrollControllerConfig) { | ||
if (config) { | ||
this.config = { ...this.config, ...config }; | ||
} | ||
|
||
if (config?.resetListeners) { | ||
this.removeEventListeners(); | ||
} | ||
|
||
this.configureListeners(); | ||
} | ||
|
||
public hostConnected() { | ||
this.configureListeners(); | ||
} | ||
|
||
public hostDisconnected() { | ||
this.removeEventListeners(); | ||
} | ||
} | ||
|
||
export function addRootScrollHandler( | ||
host: RootScrollControllerHost, | ||
config?: RootScrollControllerConfig | ||
) { | ||
return new RootScrollController(host, config); | ||
} |
Oops, something went wrong.