feat(): Added badge, card, gesture, slides, toggle

This commit is contained in:
Josh Thomas
2017-05-11 13:09:52 -05:00
parent 39e12b1c2e
commit ef0d9a89b6
20 changed files with 300 additions and 139 deletions

View File

@ -16,11 +16,9 @@ $badge-ios-text-color: color-contrast($colors-ios, $badge-ios-background
.badge-ios { .badge-ios {
@include host-context() {
border-radius: $badge-ios-border-radius; border-radius: $badge-ios-border-radius;
color: $badge-ios-text-color; color: $badge-ios-text-color;
background-color: $badge-ios-background-color; background-color: $badge-ios-background-color;
}
} }
@ -30,10 +28,8 @@ $badge-ios-text-color: color-contrast($colors-ios, $badge-ios-background
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) { @each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
.badge-ios-#{$color-name} { .badge-ios-#{$color-name} {
@include host-context() {
color: $color-contrast; color: $color-contrast;
background-color: $color-base; background-color: $color-base;
} }
}
} }

View File

@ -16,20 +16,9 @@ $badge-md-text-color: color-contrast($colors-md, $badge-md-backgro
.badge-md { .badge-md {
@include host-context() {
border-radius: $badge-md-border-radius; border-radius: $badge-md-border-radius;
color: $badge-md-text-color; color: $badge-md-text-color;
background-color: $badge-md-background-color; background-color: $badge-md-background-color;
}
}
.badge-md {
@include host-context() {
border-radius: $badge-md-border-radius;
color: $badge-md-text-color;
background-color: $badge-md-background-color;
}
} }
@ -39,10 +28,8 @@ $badge-md-text-color: color-contrast($colors-md, $badge-md-backgro
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) { @each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
.badge-md-#{$color-name} { .badge-md-#{$color-name} {
@include host-context() {
color: $color-contrast; color: $color-contrast;
background-color: $color-base; background-color: $color-base;
} }
}
} }

View File

@ -12,15 +12,12 @@ $badge-font-weight: bold !default;
ion-badge { ion-badge {
@include host() {
display: inline-block; display: inline-block;
visibility: inherit !important; visibility: inherit !important;
contain: content; contain: content;
}
} }
.badge { .badge {
@include host() {
padding: 3px 8px; padding: 3px 8px;
min-width: 10px; min-width: 10px;
@ -32,11 +29,8 @@ ion-badge {
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
vertical-align: baseline; vertical-align: baseline;
}
} }
ion-badge:empty { ion-badge:empty {
@include host(':empty') {
display: none; display: none;
}
} }

View File

@ -7,12 +7,11 @@ import { Component, h, Ionic } from '../index';
ios: 'badge.ios.scss', ios: 'badge.ios.scss',
md: 'badge.md.scss', md: 'badge.md.scss',
wp: 'badge.wp.scss' wp: 'badge.wp.scss'
} },
shadow: false
}) })
export class Badge { export class Badge {
render() { render() {
return h(this, Ionic.theme(this, 'badge'), return h(this, Ionic.theme(this, 'badge'));
h('slot')
);
} }
} }

View File

@ -16,11 +16,9 @@ $badge-wp-text-color: color-contrast($colors-wp, $badge-wp-backgro
.badge-wp { .badge-wp {
@include host-context() {
border-radius: $badge-wp-border-radius; border-radius: $badge-wp-border-radius;
color: $badge-wp-text-color; color: $badge-wp-text-color;
background-color: $badge-wp-background-color; background-color: $badge-wp-background-color;
}
} }
@ -30,10 +28,8 @@ $badge-wp-text-color: color-contrast($colors-wp, $badge-wp-backgro
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) { @each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
.badge-wp-#{$color-name} { .badge-wp-#{$color-name} {
@include host-context() {
color: $color-contrast; color: $color-contrast;
background-color: $color-base; background-color: $color-base;
} }
}
} }

View File

@ -4,8 +4,7 @@
// Card Content // Card Content
// -------------------------------------------------- // --------------------------------------------------
ion-card-content, ion-card-content {
:host {
display: block; display: block;
visibility: inherit !important; visibility: inherit !important;
} }

View File

@ -7,12 +7,11 @@ import { Component, h, Ionic } from '../index';
ios: 'card-content.ios.scss', ios: 'card-content.ios.scss',
md: 'card-content.md.scss', md: 'card-content.md.scss',
wp: 'card-content.wp.scss' wp: 'card-content.wp.scss'
} },
shadow: false
}) })
export class CardContent { export class CardContent {
render() { render() {
return h(this, Ionic.theme(this, 'card-content'), return h(this, Ionic.theme(this, 'card-content'));
h('slot')
);
} }
} }

View File

@ -5,8 +5,7 @@
// -------------------------------------------------- // --------------------------------------------------
ion-card-header, ion-card-header {
:host {
display: block; display: block;
overflow: hidden; overflow: hidden;
visibility: inherit !important; visibility: inherit !important;

View File

@ -7,12 +7,11 @@ import { Component, h, Ionic } from '../index';
ios: 'card-header.ios.scss', ios: 'card-header.ios.scss',
md: 'card-header.md.scss', md: 'card-header.md.scss',
wp: 'card-header.wp.scss' wp: 'card-header.wp.scss'
} },
shadow: false
}) })
export class CardHeader { export class CardHeader {
render() { render() {
return h(this, Ionic.theme(this, 'card-header'), return h(this, Ionic.theme(this, 'card-header'));
h('slot')
);
} }
} }

View File

@ -4,8 +4,7 @@
// Card Title // Card Title
// -------------------------------------------------- // --------------------------------------------------
ion-card-title, ion-card-title {
:host {
display: block; display: block;
visibility: inherit !important; visibility: inherit !important;
} }

View File

@ -7,12 +7,11 @@ import { Component, h, Ionic } from '../index';
ios: 'card-title.ios.scss', ios: 'card-title.ios.scss',
md: 'card-title.md.scss', md: 'card-title.md.scss',
wp: 'card-title.wp.scss' wp: 'card-title.wp.scss'
} },
shadow: false
}) })
export class CardTitle { export class CardTitle {
render() { render() {
return h(this, Ionic.theme(this, 'card-title'), return h(this, Ionic.theme(this, 'card-title'));
h('slot')
);
} }
} }

View File

@ -4,8 +4,7 @@
// -------------------------------------------------- // --------------------------------------------------
ion-card, ion-card {
:host {
display: block; display: block;
overflow: hidden; overflow: hidden;
visibility: inherit !important; visibility: inherit !important;

View File

@ -7,12 +7,11 @@ import { Component, h, Ionic } from '../index';
ios: 'card.ios.scss', ios: 'card.ios.scss',
md: 'card.md.scss', md: 'card.md.scss',
wp: 'card.wp.scss' wp: 'card.wp.scss'
} },
shadow: false
}) })
export class Card { export class Card {
render() { render() {
return h(this, Ionic.theme(this, 'card'), return h(this, Ionic.theme(this, 'card'));
h('slot')
);
} }
} }

View File

@ -1,5 +1,5 @@
import { applyStyles, getElementReference, pointerCoordX, pointerCoordY } from '../../util/helpers'; import { applyStyles, getElementReference, pointerCoordX, pointerCoordY } from '../../util/helpers';
import { Component, Listen, Ionic, Prop } from '../index'; import { Component, Ionic, Listen, Prop } from '../index';
import { GestureCallback, GestureDetail } from '../../util/interfaces'; import { GestureCallback, GestureDetail } from '../../util/interfaces';
import { GestureController, GestureDelegate } from './gesture-controller'; import { GestureController, GestureDelegate } from './gesture-controller';
import { PanRecognizer } from './recognizers'; import { PanRecognizer } from './recognizers';
@ -25,7 +25,7 @@ export class Gesture {
@Prop() direction: string = 'x'; @Prop() direction: string = 'x';
@Prop() gestureName: string = ''; @Prop() gestureName: string = '';
@Prop() gesturePriority: number = 0; @Prop() gesturePriority: number = 0;
@Prop() listenOn: string = 'child'; @Prop() attachTo: string = 'child';
@Prop() maxAngle: number = 40; @Prop() maxAngle: number = 40;
@Prop() threshold: number = 20; @Prop() threshold: number = 20;
@Prop() type: string = 'pan'; @Prop() type: string = 'pan';
@ -52,11 +52,11 @@ export class Gesture {
this.hasPress = (types.indexOf('press') > -1); this.hasPress = (types.indexOf('press') > -1);
if (this.pan || this.hasPress) { if (this.pan || this.hasPress) {
Ionic.listener.enable(this, 'touchstart', true, this.listenOn); Ionic.listener.enable(this, 'touchstart', true, this.attachTo);
Ionic.listener.enable(this, 'mousedown', true, this.listenOn); Ionic.listener.enable(this, 'mousedown', true, this.attachTo);
Ionic.dom.write(() => { Ionic.dom.write(() => {
applyStyles(getElementReference(this.$el, this.listenOn), GESTURE_INLINE_STYLES); applyStyles(getElementReference(this.$el, this.attachTo), GESTURE_INLINE_STYLES);
}); });
} }
} }

View File

@ -10,6 +10,8 @@ export declare const Ionic: interfaces.Ionic;
export declare const Listen: interfaces.ListenDecorator; export declare const Listen: interfaces.ListenDecorator;
export declare const Method: interfaces.MethodDecorator;
export declare const Prop: interfaces.PropDecorator; export declare const Prop: interfaces.PropDecorator;
export declare const Watch: interfaces.WatchDecorator; export declare const Watch: interfaces.WatchDecorator;

View File

@ -529,7 +529,7 @@ export class Slides {
/** /**
* @hidden * @hidden
*/ */
ngOnDestroy() { ionViewWillUnload() {
this._init = false; this._init = false;
this.swiper.destroy(true, true); this.swiper.destroy(true, true);

View File

@ -1,5 +1,5 @@
import { BooleanInputComponent, GestureDetail } from '../../util/interfaces'; import { BooleanInputComponent, GestureDetail } from '../../util/interfaces';
import { Component, h, Ionic, Listen, Prop, Watch } from '../index'; import { Component, h, Ionic, Listen, Method, Prop, Watch } from '../index';
@Component({ @Component({
@ -28,18 +28,18 @@ export class Toggle implements BooleanInputComponent {
} }
canStart() { private canStart() {
return !this.disabled; return !this.disabled;
} }
onDragStart(detail: GestureDetail) { private onDragStart(detail: GestureDetail) {
this.startX = detail.startX; this.startX = detail.startX;
this.fireFocus(); this.fireFocus();
} }
onDragMove(detail: GestureDetail) { private onDragMove(detail: GestureDetail) {
if (this.checked) { if (this.checked) {
if (detail.currentX + 15 < this.startX) { if (detail.currentX + 15 < this.startX) {
this.checked = false; this.checked = false;
@ -55,7 +55,7 @@ export class Toggle implements BooleanInputComponent {
} }
onDragEnd(detail: GestureDetail) { private onDragEnd(detail: GestureDetail) {
if (this.checked) { if (this.checked) {
if (detail.startX + 4 > detail.currentX) { if (detail.startX + 4 > detail.currentX) {
this.checked = false; this.checked = false;
@ -78,12 +78,13 @@ export class Toggle implements BooleanInputComponent {
ev.preventDefault(); ev.preventDefault();
} }
@Method()
toggle() { toggle() {
if (!this.disabled) { if (!this.disabled) {
this.checked = !this.checked; this.checked = !this.checked;
this.fireFocus(); this.fireFocus();
} }
return this.checked;
} }
@ -122,7 +123,7 @@ export class Toggle implements BooleanInputComponent {
'type': 'pan,press', 'type': 'pan,press',
'direction': 'x', 'direction': 'x',
'threshold': 20, 'threshold': 20,
'listenOn': 'parent' 'attachTo': 'parent'
} }
}), }),
[ [

View File

@ -160,7 +160,6 @@
// Based on a selector, apply the content to that selector and the :host // Based on a selector, apply the content to that selector and the :host
@mixin host($selector: null) { @mixin host($selector: null) {
@debug $selector;
@at-root { @at-root {
@if ($selector) { @if ($selector) {
:host(#{$selector}), :host(#{$selector}),

View File

@ -108,3 +108,20 @@ export function applyStyles(elm: HTMLElement, styles: {[styleProp: string]: stri
(<any>elm.style)[styleProps[i]] = styles[styleProps[i]]; (<any>elm.style)[styleProps[i]] = styles[styleProps[i]];
} }
} }
export function asyncFn(queue: Function[], cb: Function) {
if (queue === null) {
cb();
} else {
queue.push(cb);
}
}
export function drainAsyncFns(queue: Function[]): any {
if (queue) {
for (var i = 0; i < queue.length; i++) {
queue[i]();
}
}
return null;
}

View File

@ -3,6 +3,7 @@ export interface Ionic {
emit: EventEmit; emit: EventEmit;
listener: { listener: {
enable: EventListenerEnable; enable: EventListenerEnable;
add: AddEventListenerApi;
}; };
theme: IonicTheme; theme: IonicTheme;
controllers: { controllers: {
@ -10,6 +11,69 @@ export interface Ionic {
}; };
dom: DomControllerApi; dom: DomControllerApi;
config: ConfigApi; config: ConfigApi;
modal: ModalControllerApi;
Animation: Animation;
}
export interface IonicGlobal {
staticDir?: string;
components?: LoadComponents;
loadComponents?: (coreVersion: number, bundleId: string, modulesImporterFn: ModulesImporterFn, cmp0?: ComponentModeData, cmp1?: ComponentModeData, cmp2?: ComponentModeData) => void;
eventNameFn?: (eventName: string) => string;
config?: Object;
ConfigCtrl?: ConfigApi;
DomCtrl?: DomControllerApi;
NextTickCtrl?: NextTickApi;
Animation: any;
}
export interface ModalControllerApi {
create: (component: string, params?: any, opts?: ModalOptions) => Promise<Modal>;
}
export interface ModalControllerInternalApi extends ModalControllerApi {
_create?: any[];
}
export interface Modal {
component: string;
id: string;
style?: {
zIndex: number;
};
showBackdrop: boolean;
enableBackdropDismiss: boolean;
enterAnimation: AnimationBuilder;
exitAnimation: AnimationBuilder;
cssClass: string;
params: any;
present: (done?: Function) => void;
dismiss: (done?: Function) => void;
}
export interface ModalOptions {
showBackdrop?: boolean;
enableBackdropDismiss?: boolean;
enterAnimation?: AnimationBuilder;
exitAnimation?: AnimationBuilder;
cssClass?: string;
}
export interface ModalEvent extends Event {
detail: {
modal: Modal;
};
}
export interface AddEventListenerApi {
(elm: HTMLElement|HTMLDocument|Window, eventName: string, cb: (ev?: any) => void, opts?: ListenOptions): Function;
} }
@ -27,7 +91,7 @@ export interface CustomEventOptions {
export interface EventListenerEnable { export interface EventListenerEnable {
(instance: any, eventName: string, enabled: boolean, listenOn?: string): void; (instance: any, eventName: string, enabled: boolean, attachTo?: string): void;
} }
@ -100,18 +164,6 @@ export interface ContentDimensions {
} }
export interface IonicGlobal {
staticDir?: string;
components?: LoadComponents;
loadComponents?: {(bundleId: string, modulesImporterFn: ModulesImporterFn, cmp0?: ComponentModeData, cmp1?: ComponentModeData, cmp2?: ComponentModeData): void};
eventNameFn?: {(eventName: string): string};
config?: Object;
ConfigCtrl?: ConfigApi;
DomCtrl?: DomControllerApi;
NextTickCtrl?: NextTickApi;
}
export interface NextTickApi { export interface NextTickApi {
nextTick: NextTick; nextTick: NextTick;
} }
@ -150,9 +202,9 @@ export interface ComponentModeData {
[0]: string; [0]: string;
/** /**
* component class name (Badge) * methods
*/ */
[1]: string; [1]: Methods;
/** /**
* listeners * listeners
@ -245,22 +297,29 @@ export interface PropOptions {
} }
export interface Props { export interface PropMeta {
[propName: string]: PropOptions; propName?: string;
propType?: any;
} }
export type Methods = string[];
export interface MethodDecorator {
(opts?: MethodOptions): any;
}
export interface MethodOptions {}
export interface ListenDecorator { export interface ListenDecorator {
(eventName: string, opts?: ListenOpts): any; (eventName: string, opts?: ListenOptions): any;
} }
export interface ComponentMetaListeners { export interface ListenOptions {
[methodName: string]: ListenOpts;
}
export interface ListenOpts {
eventName?: string; eventName?: string;
capture?: boolean; capture?: boolean;
passive?: boolean; passive?: boolean;
@ -268,6 +327,11 @@ export interface ListenOpts {
} }
export interface ListenMeta extends ListenOptions {
methodName?: string;
}
export interface WatchDecorator { export interface WatchDecorator {
(propName: string): any; (propName: string): any;
} }
@ -278,8 +342,8 @@ export interface WatchOpts {
} }
export interface Watchers { export interface WatchMeta extends WatchOpts {
[propName: string]: WatchOpts; propName?: string;
} }
@ -297,18 +361,21 @@ export interface ConfigApi {
export interface ComponentMeta { export interface ComponentMeta {
tag?: string; tag?: string;
props?: Props; methods?: Methods;
listeners?: ComponentMetaListeners; props?: PropMeta[];
watchers?: Watchers; listeners?: ListenMeta[];
watchers?: WatchMeta[];
modes: ModeMeta[];
shadow?: boolean; shadow?: boolean;
namedSlots?: string[];
obsAttrs?: string[]; obsAttrs?: string[];
componentModule?: any; componentModule?: any;
modes: {[modeName: string]: ComponentMode};
priority?: 'high'|'low'; priority?: 'high'|'low';
} }
export interface ComponentMode { export interface ModeMeta {
modeName?: string;
bundleId?: string; bundleId?: string;
styles?: string; styles?: string;
styleUrls?: string[]; styleUrls?: string[];
@ -317,10 +384,10 @@ export interface ComponentMode {
export interface Component { export interface Component {
ionViewDidLoad?: {(): void}; ionViewDidLoad?: () => void;
ionViewWillUnload?: {(): void}; ionViewWillUnload?: () => void;
render?: {(): VNode}; render?: () => VNode;
mode?: string; mode?: string;
color?: string; color?: string;
@ -328,8 +395,10 @@ export interface Component {
$el?: ProxyElement; $el?: ProxyElement;
$meta?: ComponentMeta; $meta?: ComponentMeta;
$listeners?: ComponentActiveListeners; $listeners?: ComponentActiveListeners;
$watchers?: ComponentActiveWatchers;
$root?: HTMLElement | ShadowRoot; $root?: HTMLElement | ShadowRoot;
$vnode?: VNode; $vnode?: VNode;
$values?: ComponentActiveValues;
[memberName: string]: any; [memberName: string]: any;
} }
@ -340,19 +409,27 @@ export interface ComponentActiveListeners {
} }
export type ComponentActiveWatchers = Function[];
export interface ComponentActiveValues {
[propName: string]: any;
}
export interface BaseInputComponent extends Component { export interface BaseInputComponent extends Component {
disabled: boolean; disabled: boolean;
hasFocus: boolean; hasFocus: boolean;
value: string; value: string;
fireFocus: {(): void}; fireFocus: () => void;
fireBlur: {(): void}; fireBlur: () => void;
} }
export interface BooleanInputComponent extends BaseInputComponent { export interface BooleanInputComponent extends BaseInputComponent {
checked: boolean; checked: boolean;
toggle: {(ev: UIEvent): void}; toggle: (ev: UIEvent) => void;
} }
@ -367,22 +444,28 @@ export interface ComponentRegistry {
export interface ProxyElement extends HTMLElement { export interface ProxyElement extends HTMLElement {
connectedCallback: {(): void}; connectedCallback: () => void;
attributeChangedCallback: {(attrName: string, oldVal: string, newVal: string, namespace: string): void}; attributeChangedCallback: (attrName: string, oldVal: string, newVal: string, namespace: string) => void;
disconnectedCallback: {(): void}; disconnectedCallback: () => void;
$queueUpdate: () => void;
$queued?: boolean; $queued?: boolean;
$instance?: Component; $instance?: Component;
$hostContent?: HostContentNodes;
$tmpDisconnected?: boolean;
[memberName: string]: any; [memberName: string]: any;
} }
export type QueueHandlerId = number;
export type Side = 'left' | 'right' | 'start' | 'end'; export type Side = 'left' | 'right' | 'start' | 'end';
export interface RendererApi { export interface RendererApi {
(oldVnode: VNode | Element, vnode: VNode, manualSlotProjection?: boolean): VNode; (oldVnode: VNode | Element, vnode: VNode, hostContentNodes?: HostContentNodes): VNode;
} }
@ -411,6 +494,12 @@ export interface VNode {
} }
export interface HostContentNodes {
$defaultSlot: Node[];
$namedSlots?: {[slotName: string]: Node[]};
}
export interface VNodeData { export interface VNodeData {
props?: any; props?: any;
attrs?: any; attrs?: any;
@ -418,7 +507,7 @@ export interface VNodeData {
style?: any; style?: any;
dataset?: any; dataset?: any;
on?: any; on?: any;
attachData?: any; ref?: (elm: any) => void;
vkey?: Key; vkey?: Key;
vns?: string; // for SVGs vns?: string; // for SVGs
[key: string]: any; // for any other 3rd party module [key: string]: any; // for any other 3rd party module
@ -428,7 +517,7 @@ export interface VNodeData {
export interface PlatformApi { export interface PlatformApi {
registerComponent: (tag: string, data: any[]) => ComponentMeta; registerComponent: (tag: string, data: any[]) => ComponentMeta;
getComponentMeta: (tag: string) => ComponentMeta; getComponentMeta: (tag: string) => ComponentMeta;
loadComponent: (bundleId: string, priority: string, cb: Function) => void; loadBundle: (bundleId: string, priority: string, cb: Function) => void;
nextTick: NextTick; nextTick: NextTick;
isElement: (node: Node) => node is Element; isElement: (node: Node) => node is Element;
@ -463,3 +552,92 @@ export interface ServerInitConfig {
staticDir: string; staticDir: string;
config?: Object; config?: Object;
} }
export interface Animation {
new(elm?: Node|Node[]|NodeList): Animation;
addChildAnimation: (childAnimation: Animation) => Animation;
addElement: (elm: Node|Node[]|NodeList) => Animation;
afterAddClass: (className: string) => Animation;
afterClearStyles: (propertyNames: string[]) => Animation;
afterRemoveClass: (className: string) => Animation;
afterStyles: (styles: { [property: string]: any; }) => Animation;
beforeAddClass: (className: string) => Animation;
beforeClearStyles: (propertyNames: string[]) => Animation;
beforeRemoveClass: (className: string) => Animation;
beforeStyles: (styles: { [property: string]: any; }) => Animation;
destroy: () => void;
duration: (milliseconds: number) => Animation;
easing: (name: string) => Animation;
from: (prop: string, val: any) => Animation;
fromTo: (prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean) => Animation;
hasCompleted: boolean;
isPlaying: boolean;
onFinish: (callback: (animation?: Animation) => void, opts?: {oneTimeCallback: boolean, clearExistingCallacks: boolean}) => Animation;
play: (opts?: PlayOptions) => void;
progressEnd: (shouldComplete: boolean, currentStepValue: number, dur: number) => void;
progressStep: (stepValue: number) => void;
progressStart: () => void;
reverse: (shouldReverse?: boolean) => Animation;
stop: (stepValue?: number) => void;
to: (prop: string, val: any, clearProperyAfterTransition?: boolean) => Animation;
}
export interface AnimationBuilder {
(elm?: HTMLElement): Animation;
}
export interface AnimationOptions {
animation?: string;
duration?: number;
easing?: string;
direction?: string;
isRTL?: boolean;
ev?: any;
}
export interface PlayOptions {
duration?: number;
promise?: boolean;
}
export interface EffectProperty {
effectName: string;
trans: boolean;
wc?: string;
to?: EffectState;
from?: EffectState;
[state: string]: any;
}
export interface EffectState {
val: any;
num: number;
effectUnit: string;
}
export interface RequestIdleCallback {
(callback: IdleCallback): number;
}
export interface IdleCallback {
(deadline: IdleDeadline, options?: IdleOptions): void;
}
export interface IdleDeadline {
didTimeout: boolean;
timeRemaining: () => number;
}
export interface IdleOptions {
timeout?: number;
}