Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor toggle directive into popover component #983

Merged
merged 28 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
787ca06
feat: Refactor toggle directive into popover component
rkaraivanov Nov 10, 2023
d70fee4
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 14, 2023
cd5de16
refactor(dropdown): Migrated to popover component
rkaraivanov Nov 15, 2023
96d0edc
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 15, 2023
a45bd85
refactor(dropdown): Finalized dropdown class
rkaraivanov Nov 15, 2023
bfd3497
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 16, 2023
5b54032
Merge branch 'master' into rkaraivanov/popover-component
simeonoff Nov 16, 2023
751b03b
Merge branch 'rkaraivanov/popover-component' of https://github.com/Ig…
rkaraivanov Nov 20, 2023
718ae83
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 20, 2023
c24b602
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 27, 2023
bbfc187
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Nov 27, 2023
3cce626
refactor(select): Use the underlying popover
rkaraivanov Nov 27, 2023
add2abd
refactor: Code reorganization and cleanups
rkaraivanov Nov 28, 2023
7b67c6c
refactor(dropdown): Detached target behavior
rkaraivanov Nov 29, 2023
81fdd3e
fix: Types lint error
rkaraivanov Nov 29, 2023
6b05595
refactor: Finalized igc-select
rkaraivanov Nov 29, 2023
7370805
refactor: Scroll strategy implementation
rkaraivanov Nov 30, 2023
e74446d
refactor: Moved keybindings to handleEvent pattern
rkaraivanov Dec 1, 2023
3ac90d2
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Dec 1, 2023
6175866
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Dec 1, 2023
a252092
Merge branch 'master' into rkaraivanov/popover-component
simeonoff Dec 1, 2023
3fa9d76
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Dec 1, 2023
85dea5b
Merge branch 'rkaraivanov/popover-component' of https://github.com/Ig…
rkaraivanov Dec 4, 2023
064328c
chore: Improved storybook samples
rkaraivanov Dec 4, 2023
403d6ce
Merge branch 'master' of https://github.com/IgniteUI/igniteui-webcomp…
rkaraivanov Dec 4, 2023
65614e4
refactor: RootClickControllerConfig as type
rkaraivanov Dec 4, 2023
bf9696f
fix: SB dropdown samples
rkaraivanov Dec 5, 2023
83fee64
Merge branch 'master' into rkaraivanov/popover-component
ChronosSF Dec 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { addons } from '@storybook/manager-api';

addons.setConfig({
enableShortcuts: false,
});
6 changes: 6 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { html } from 'lit';
import { configureTheme } from '../src/theming/config';
import type { Decorator } from '@storybook/web-components';
import { withActions } from '@storybook/addon-actions/decorator';
import { configureActions } from '@storybook/addon-actions';

configureActions({
clearOnStoryChange: true,
limit: 5,
});

type ThemeImport = { default: string };

Expand Down
45 changes: 32 additions & 13 deletions src/components/common/controllers/key-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const arrowUp = 'ArrowUp' as const;
export const arrowDown = 'ArrowDown' as const;
export const enterKey = 'Enter' as const;
export const spaceBar = ' ' as const;
export const escapeKey = 'Escape' as const;
export const homeKey = 'Home' as const;
export const endKey = 'End' as const;
export const pageUpKey = 'PageUp' as const;
Expand All @@ -22,6 +23,7 @@ export const shiftKey = 'Shift' as const;

/* Types */
export type KeyBindingHandler = (event: KeyboardEvent) => void;
export type KeyBindingObserverCleanup = { unsubscribe: () => void };

/**
* Whether the current event should be ignored by the controller.
Expand Down Expand Up @@ -169,14 +171,39 @@ export function parseKeys(keys: string | string[]) {
class KeyBindingController implements ReactiveController {
protected _host: ReactiveControllerHost & Element;
protected _ref?: Ref;
protected _observedElement?: Element;
protected _options?: KeyBindingControllerOptions;
private bindings = new Set<KeyBinding>();
private pressedKeys = new Set<string>();

protected get _element() {
if (this._observedElement) {
return this._observedElement;
}
return this._ref ? this._ref.value : this._host;
}

/**
* Sets the controller to listen for keyboard events on an arbitrary `element` in the page context.
* All the configuration and event handlers are applied as well.
*
* Returns an object with an `unsubscribe` function which should be called when the observing of keyboard
* events on the `element` should cease.
*/
public observeElement(element: Element): KeyBindingObserverCleanup {
element.addEventListener('keydown', this);
element.addEventListener('keyup', this);
this._observedElement = element;

return {
unsubscribe: () => {
this._observedElement?.removeEventListener('keydown', this);
this._observedElement?.removeEventListener('keyup', this);
this._observedElement = undefined;
},
};
}

constructor(
host: ReactiveControllerHost & Element,
options?: KeyBindingControllerOptions
Expand Down Expand Up @@ -226,7 +253,7 @@ class KeyBindingController implements ReactiveController {
return false;
}

private _handleEvent(event: KeyboardEvent) {
public handleEvent(event: KeyboardEvent) {
const key = event.key.toLowerCase();
const path = event.composedPath();
const skip = this._options?.skip;
Expand Down Expand Up @@ -274,14 +301,6 @@ class KeyBindingController implements ReactiveController {
}
}

private onKeyUp = (event: Event) => {
this._handleEvent(event as KeyboardEvent);
};

private onKeyDown = (event: Event) => {
this._handleEvent(event as KeyboardEvent);
};

/**
* Registers a keybinding handler.
*/
Expand Down Expand Up @@ -317,13 +336,13 @@ class KeyBindingController implements ReactiveController {
}

public hostConnected(): void {
this._host.addEventListener('keyup', this.onKeyUp);
this._host.addEventListener('keydown', this.onKeyDown);
this._host.addEventListener('keyup', this);
this._host.addEventListener('keydown', this);
}

public hostDisconnected(): void {
this._host.removeEventListener('keyup', this.onKeyUp);
this._host.removeEventListener('keydown', this.onKeyDown);
this._host.removeEventListener('keyup', this);
this._host.removeEventListener('keydown', this);
}
}

Expand Down
78 changes: 78 additions & 0 deletions src/components/common/controllers/root-click.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { ReactiveController, ReactiveControllerHost } from 'lit';

interface RootClickControllerConfig {
hideCallback?: Function;
target?: HTMLElement;
}
simeonoff marked this conversation as resolved.
Show resolved Hide resolved

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);
simeonoff marked this conversation as resolved.
Show resolved Hide resolved
}
}

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);
}
98 changes: 98 additions & 0 deletions src/components/common/controllers/root-scroll.ts
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;
simeonoff marked this conversation as resolved.
Show resolved Hide resolved

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);
}
Loading
Loading