vanilla-jui 1.4.7 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/index.d.ts +418 -970
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -3
- package/src/components/accordion.ts +466 -346
- package/src/components/flow.ts +779 -1133
- package/src/components/form.ts +666 -509
- package/src/components/menu.ts +198 -328
- package/src/components/modal.ts +510 -747
- package/src/components/offcanvas.ts +273 -367
- package/src/components/pagination.ts +201 -375
- package/src/components/swiper.ts +689 -1107
- package/src/components/tabs.ts +467 -778
- package/src/components/toc.ts +191 -244
- package/src/core/component.ts +332 -0
- package/src/core/motion.ts +298 -0
- package/src/core/presence.ts +233 -0
- package/src/core/scheduler.ts +91 -0
- package/src/core/view.ts +31 -0
- package/src/css/accordion.css +94 -3
- package/src/css/content.css +18 -24
- package/src/css/drop.css +4 -1
- package/src/css/form.css +8 -0
- package/src/css/menu.css +10 -5
- package/src/css/modal.css +2 -1
- package/src/css/offcanvas.css +5 -17
- package/src/css/swiper.css +2 -1
- package/src/css/tabs.css +8 -17
- package/src/css/themes/default/accordion.css +0 -48
- package/src/css/themes/default/swiper.css +1 -1
- package/src/css/themes/default/tabs.css +4 -6
- package/src/css/toast.css +1 -17
- package/src/css/tooltip.css +2 -1
- package/src/index.ts +8 -2
- package/src/locales/index.ts +3 -0
- package/src/primitives/drop.ts +253 -262
- package/src/primitives/loading.ts +22 -4
- package/src/primitives/parabola.ts +123 -164
- package/src/primitives/sticky.ts +297 -0
- package/src/primitives/theme.ts +164 -222
- package/src/primitives/toast.ts +309 -284
- package/src/primitives/tooltip.ts +65 -66
- package/src/utilities/browser.ts +38 -0
- package/src/utilities/dom.ts +0 -54
- package/src/utilities/refs.ts +61 -0
- package/src/utilities/state.ts +73 -0
- package/src/utilities/timer.ts +11 -31
- package/src/validation/validator.ts +289 -389
- package/src/components/sticky.ts +0 -297
- package/src/core/Component.ts +0 -377
|
@@ -23,13 +23,9 @@ interface DropDelay {
|
|
|
23
23
|
hide?: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
interface
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
interface TooltipInstance {
|
|
31
|
-
dom: TooltipDom;
|
|
32
|
-
drop: DropInstance | null;
|
|
26
|
+
export interface TooltipInstance {
|
|
27
|
+
readonly element: HTMLElement | null;
|
|
28
|
+
readonly drop: DropInstance | null;
|
|
33
29
|
show(useDelay?: boolean): void;
|
|
34
30
|
hide(useDelay?: boolean): void;
|
|
35
31
|
toggle(): void;
|
|
@@ -68,6 +64,8 @@ interface TooltipProps extends Record<string, unknown> {
|
|
|
68
64
|
offset?: number;
|
|
69
65
|
message?: string;
|
|
70
66
|
theme?: TooltipTheme;
|
|
67
|
+
cache?: boolean;
|
|
68
|
+
ttl?: number;
|
|
71
69
|
className?: TooltipClassNameConfig;
|
|
72
70
|
id?: string | null;
|
|
73
71
|
delay?: number | DropDelay;
|
|
@@ -83,6 +81,8 @@ interface ResolvedTooltipProps extends Record<string, unknown> {
|
|
|
83
81
|
offset: number;
|
|
84
82
|
message: string;
|
|
85
83
|
theme: TooltipTheme;
|
|
84
|
+
cache: boolean;
|
|
85
|
+
ttl: number;
|
|
86
86
|
className: TooltipClassNames;
|
|
87
87
|
id: string | null;
|
|
88
88
|
delay: number | DropDelay;
|
|
@@ -148,6 +148,12 @@ const TOOLTIP_OPTIONS_SCHEMA = {
|
|
|
148
148
|
types: ['string', 'boolean'],
|
|
149
149
|
enum: [false, 'reverse', 'primary', 'success', 'warning', 'error'],
|
|
150
150
|
},
|
|
151
|
+
cache: { default: false, type: 'boolean' },
|
|
152
|
+
ttl: {
|
|
153
|
+
default: 0,
|
|
154
|
+
type: 'number',
|
|
155
|
+
min: 0,
|
|
156
|
+
},
|
|
151
157
|
className: {
|
|
152
158
|
default: DEFAULT_CLASS_NAMES,
|
|
153
159
|
type: 'object',
|
|
@@ -169,6 +175,8 @@ function normalizeProps(input: TooltipProps): ResolvedTooltipProps {
|
|
|
169
175
|
offset: props.offset as number,
|
|
170
176
|
message: props.message as string,
|
|
171
177
|
theme: props.theme as TooltipTheme,
|
|
178
|
+
cache: props.cache as boolean,
|
|
179
|
+
ttl: props.ttl as number,
|
|
172
180
|
className: props.className as TooltipClassNames,
|
|
173
181
|
id: props.id as string | null,
|
|
174
182
|
delay: props.delay as number | DropDelay,
|
|
@@ -183,66 +191,57 @@ function normalizeProps(input: TooltipProps): ResolvedTooltipProps {
|
|
|
183
191
|
*
|
|
184
192
|
* 基于 Drop 实现,提供更轻量的文本提示封装。
|
|
185
193
|
*/
|
|
186
|
-
class Tooltip implements TooltipInstance {
|
|
187
|
-
dom: TooltipDom;
|
|
188
|
-
drop: DropInstance | null;
|
|
189
|
-
props: ResolvedTooltipProps | null;
|
|
190
|
-
|
|
191
|
-
constructor(element: DOMReference, props: TooltipProps = {}) {
|
|
192
|
-
const settings = normalizeProps(props);
|
|
193
|
-
this.props = settings;
|
|
194
|
-
this.drop = createDrop(element, {
|
|
195
|
-
name: settings.name,
|
|
196
|
-
mode: settings.mode,
|
|
197
|
-
position: settings.position,
|
|
198
|
-
offset: settings.offset,
|
|
199
|
-
id: settings.id,
|
|
200
|
-
delay: settings.delay,
|
|
201
|
-
hoverIntent: settings.hoverIntent,
|
|
202
|
-
onShown: settings.onShown,
|
|
203
|
-
onHidden: settings.onHidden,
|
|
204
|
-
content: this.buildContent(settings),
|
|
205
|
-
});
|
|
206
|
-
this.dom = this.drop.dom;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private buildContent(settings: ResolvedTooltipProps): HTMLElement {
|
|
210
|
-
return jsx('div', {
|
|
211
|
-
className: joinClasses(
|
|
212
|
-
settings.className.container,
|
|
213
|
-
settings.theme && settings.className.ui[settings.theme]
|
|
214
|
-
),
|
|
215
|
-
'data-tooltip': settings.name || settings.id || '',
|
|
216
|
-
children: jsx('div', {
|
|
217
|
-
className: settings.className.message,
|
|
218
|
-
'data-tooltip-message': '',
|
|
219
|
-
children: settings.message,
|
|
220
|
-
}),
|
|
221
|
-
}) as HTMLElement;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
show(useDelay = true): void {
|
|
225
|
-
this.drop?.show(useDelay);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
hide(useDelay = true): void {
|
|
229
|
-
this.drop?.hide(useDelay);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
toggle(): void {
|
|
233
|
-
this.drop?.toggle();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
destroy(): void {
|
|
237
|
-
this.drop?.destroy();
|
|
238
|
-
this.drop = null;
|
|
239
|
-
this.props = null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
194
|
export function createTooltip(
|
|
244
195
|
element: DOMReference,
|
|
245
|
-
|
|
196
|
+
input: TooltipProps = {}
|
|
246
197
|
): TooltipInstance {
|
|
247
|
-
|
|
198
|
+
const props = normalizeProps(input);
|
|
199
|
+
const content = jsx('div', {
|
|
200
|
+
className: joinClasses(
|
|
201
|
+
props.className.container,
|
|
202
|
+
props.theme && props.className.ui[props.theme]
|
|
203
|
+
),
|
|
204
|
+
'data-tooltip': props.name || props.id || '',
|
|
205
|
+
children: jsx('div', {
|
|
206
|
+
className: props.className.message,
|
|
207
|
+
'data-tooltip-message': '',
|
|
208
|
+
children: props.message,
|
|
209
|
+
}),
|
|
210
|
+
}) as HTMLElement;
|
|
211
|
+
let drop: DropInstance | null = createDrop(element, {
|
|
212
|
+
name: props.name,
|
|
213
|
+
mode: props.mode,
|
|
214
|
+
position: props.position,
|
|
215
|
+
offset: props.offset,
|
|
216
|
+
cache: props.cache,
|
|
217
|
+
ttl: props.ttl,
|
|
218
|
+
id: props.id,
|
|
219
|
+
delay: props.delay,
|
|
220
|
+
hoverIntent: props.hoverIntent,
|
|
221
|
+
onShown: props.onShown,
|
|
222
|
+
onHidden: props.onHidden,
|
|
223
|
+
content,
|
|
224
|
+
});
|
|
225
|
+
const tooltip: TooltipInstance = {
|
|
226
|
+
get element() {
|
|
227
|
+
return drop?.element || null;
|
|
228
|
+
},
|
|
229
|
+
get drop() {
|
|
230
|
+
return drop;
|
|
231
|
+
},
|
|
232
|
+
show(useDelay = true) {
|
|
233
|
+
drop?.show(useDelay);
|
|
234
|
+
},
|
|
235
|
+
hide(useDelay = true) {
|
|
236
|
+
drop?.hide(useDelay);
|
|
237
|
+
},
|
|
238
|
+
toggle() {
|
|
239
|
+
drop?.toggle();
|
|
240
|
+
},
|
|
241
|
+
destroy() {
|
|
242
|
+
drop?.destroy();
|
|
243
|
+
drop = null;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
return tooltip;
|
|
248
247
|
}
|
package/src/utilities/browser.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { t } from 'vanilla-signal-i18n';
|
|
2
|
+
import { Toast } from '../primitives/toast';
|
|
3
|
+
import locales from '../locales';
|
|
4
|
+
|
|
1
5
|
declare global {
|
|
2
6
|
interface Navigator {
|
|
3
7
|
userAgentData?: {
|
|
@@ -103,3 +107,37 @@ function fallbackCopy(text: string): boolean {
|
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
}
|
|
110
|
+
|
|
111
|
+
export type SupportES2022 = boolean;
|
|
112
|
+
export function isModernBrowser(): SupportES2022 {
|
|
113
|
+
try {
|
|
114
|
+
// Object.hasOwn()
|
|
115
|
+
if (typeof Object.hasOwn !== 'function') return false;
|
|
116
|
+
// Array.prototype.at()
|
|
117
|
+
if (typeof [].at !== 'function') return false;
|
|
118
|
+
|
|
119
|
+
// new Function('class Test { #x = 1; getX() { return this.#x; } }');
|
|
120
|
+
// new Function('const re = /a/d; return re.hasIndices;');
|
|
121
|
+
// const err = new Error('test', { cause: 'reason' });
|
|
122
|
+
// if (err.cause !== 'reason') return false;
|
|
123
|
+
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function checkModernBrowser(): SupportES2022 {
|
|
130
|
+
if (!isModernBrowser()) {
|
|
131
|
+
Toast.action(t('wn', locales), {
|
|
132
|
+
type: 'error',
|
|
133
|
+
onAction: () => {
|
|
134
|
+
window.open('https://www.google.cn/chrome/fallback/', '_blank');
|
|
135
|
+
},
|
|
136
|
+
className: {
|
|
137
|
+
actionBtn: 'j-button is-sm is-danger',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
package/src/utilities/dom.ts
CHANGED
|
@@ -104,52 +104,6 @@ function flattenNodeArray(
|
|
|
104
104
|
return out;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* 将可渲染内容归一化为 DOM 节点数组。
|
|
109
|
-
*
|
|
110
|
-
* 支持的输入类型:
|
|
111
|
-
* - **Node/Element**: 包装为单元素数组
|
|
112
|
-
* - **string**: 按 HTML 片段解析为节点数组
|
|
113
|
-
* - **number/boolean**: 转换为文本节点
|
|
114
|
-
* - **Array**: 递归扁平化处理
|
|
115
|
-
* - **Function**: 调用后递归处理返回值
|
|
116
|
-
* - **null/undefined/false/true**: 返回空数组
|
|
117
|
-
*
|
|
118
|
-
* @param {RenderableContent<TContext>} content 可渲染内容。
|
|
119
|
-
* @param {TContext} [context] 传递给函数类型内容的上下文。
|
|
120
|
-
* @returns {Node[]} 归一化后的节点数组。
|
|
121
|
-
*/
|
|
122
|
-
export function normalizeContentNodes<TContext = unknown>(
|
|
123
|
-
content: RenderableContent<TContext>,
|
|
124
|
-
context?: TContext
|
|
125
|
-
): Node[] {
|
|
126
|
-
if (typeof content === 'function') {
|
|
127
|
-
return normalizeContentNodes(content(context as TContext), context);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const value = content;
|
|
131
|
-
|
|
132
|
-
if (value == null || value === false || value === true) return [];
|
|
133
|
-
|
|
134
|
-
if (Array.isArray(value)) {
|
|
135
|
-
return value.flatMap((item) => normalizeContentNodes(item, context));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (isNode(value)) return [value];
|
|
139
|
-
|
|
140
|
-
if (typeof value === 'string') {
|
|
141
|
-
const template = document.createElement('template');
|
|
142
|
-
template.innerHTML = value;
|
|
143
|
-
return Array.from(template.content.childNodes);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return [
|
|
147
|
-
document.createTextNode(
|
|
148
|
-
String(value as string | number | boolean | bigint | symbol)
|
|
149
|
-
),
|
|
150
|
-
];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
107
|
/**
|
|
154
108
|
* 将 DOM 引用解析为节点列表。
|
|
155
109
|
*
|
|
@@ -299,14 +253,6 @@ export function isRenderableContent(
|
|
|
299
253
|
return isRenderableValue(value);
|
|
300
254
|
}
|
|
301
255
|
|
|
302
|
-
export function normalizeRenderableContentNodes<TContext>(
|
|
303
|
-
content: unknown,
|
|
304
|
-
context: TContext
|
|
305
|
-
): Node[] | null {
|
|
306
|
-
if (!isRenderableContent(content)) return null;
|
|
307
|
-
return normalizeContentNodes(content, context);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
256
|
/**
|
|
311
257
|
* 根据 CSS 选择器获取第一个匹配的元素。
|
|
312
258
|
* @param {string} selector CSS 选择器。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { onCleanup } from 'vanilla-signal';
|
|
2
|
+
|
|
3
|
+
export interface ElementRef<TElement extends Element> {
|
|
4
|
+
readonly current: TElement | null;
|
|
5
|
+
set: (element: TElement) => void;
|
|
6
|
+
clear: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createElementRef<
|
|
10
|
+
TElement extends Element,
|
|
11
|
+
>(): ElementRef<TElement> {
|
|
12
|
+
let current: TElement | null = null;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
get current() {
|
|
16
|
+
return current;
|
|
17
|
+
},
|
|
18
|
+
set(element) {
|
|
19
|
+
current = element;
|
|
20
|
+
},
|
|
21
|
+
clear() {
|
|
22
|
+
current = null;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface KeyedElementRefs<TKey, TElement extends Element> {
|
|
28
|
+
readonly elements: ReadonlyMap<TKey, TElement>;
|
|
29
|
+
get: (key: TKey) => TElement | undefined;
|
|
30
|
+
bind: (key: TKey) => (element: TElement) => void;
|
|
31
|
+
delete: (key: TKey) => void;
|
|
32
|
+
clear: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createKeyedElementRefs<
|
|
36
|
+
TKey,
|
|
37
|
+
TElement extends Element,
|
|
38
|
+
>(): KeyedElementRefs<TKey, TElement> {
|
|
39
|
+
const elements = new Map<TKey, TElement>();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
elements,
|
|
43
|
+
get(key) {
|
|
44
|
+
return elements.get(key);
|
|
45
|
+
},
|
|
46
|
+
bind(key) {
|
|
47
|
+
return (element) => {
|
|
48
|
+
elements.set(key, element);
|
|
49
|
+
onCleanup(() => {
|
|
50
|
+
if (elements.get(key) === element) elements.delete(key);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
delete(key) {
|
|
55
|
+
elements.delete(key);
|
|
56
|
+
},
|
|
57
|
+
clear() {
|
|
58
|
+
elements.clear();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createWatch, untrack, unwrap } from 'vanilla-signal';
|
|
2
|
+
|
|
3
|
+
import { createScheduledTask } from '../core/scheduler.ts';
|
|
4
|
+
|
|
5
|
+
export interface StateSyncOptions {
|
|
6
|
+
deferInitial?: boolean;
|
|
7
|
+
flushInitial?: boolean;
|
|
8
|
+
flush?: 'microtask' | 'sync';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getStoreVersion(value: unknown): number {
|
|
12
|
+
if (value && typeof value === 'object') {
|
|
13
|
+
const version = (value as { __version__?: unknown }).__version__;
|
|
14
|
+
return typeof version === 'number' ? version : 0;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function trackStoreVersion<T>(value: T): T {
|
|
20
|
+
getStoreVersion(value);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function stateSnapshot<T>(value: T): T {
|
|
25
|
+
return unwrap(value) as T;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Bridges reactive state to an expensive imperative effect. Declarative view
|
|
30
|
+
* bindings should depend on state directly instead of using this helper.
|
|
31
|
+
*/
|
|
32
|
+
export function createStateSync<TSnapshot>(
|
|
33
|
+
read: () => TSnapshot,
|
|
34
|
+
sync: (snapshot: TSnapshot) => void | Promise<void>,
|
|
35
|
+
{
|
|
36
|
+
deferInitial = true,
|
|
37
|
+
flushInitial = false,
|
|
38
|
+
flush = 'microtask',
|
|
39
|
+
}: StateSyncOptions = {}
|
|
40
|
+
): () => void {
|
|
41
|
+
let initialized = false;
|
|
42
|
+
let hasSnapshot = false;
|
|
43
|
+
let latestSnapshot: TSnapshot;
|
|
44
|
+
|
|
45
|
+
const task = createScheduledTask(() => {
|
|
46
|
+
if (!hasSnapshot) return;
|
|
47
|
+
hasSnapshot = false;
|
|
48
|
+
void untrack(() => sync(latestSnapshot));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const watcher = createWatch(
|
|
52
|
+
read,
|
|
53
|
+
(snapshot) => {
|
|
54
|
+
const isInitial = !initialized;
|
|
55
|
+
initialized = true;
|
|
56
|
+
latestSnapshot = snapshot as TSnapshot;
|
|
57
|
+
hasSnapshot = true;
|
|
58
|
+
|
|
59
|
+
if ((isInitial && flushInitial) || flush === 'sync') {
|
|
60
|
+
task.flush();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
task.schedule();
|
|
65
|
+
},
|
|
66
|
+
{ defer: deferInitial }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
task.dispose();
|
|
71
|
+
watcher.dispose();
|
|
72
|
+
};
|
|
73
|
+
}
|
package/src/utilities/timer.ts
CHANGED
|
@@ -1,38 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
* 定时器管理器
|
|
3
|
-
*/
|
|
4
|
-
export const timer = {
|
|
5
|
-
// 存储定时器 { key: timerId }
|
|
6
|
-
timers: {} as Record<string, ReturnType<typeof setTimeout>>,
|
|
1
|
+
const timers: Record<string, ReturnType<typeof setTimeout>> = {};
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
start(key: string, duration: number, callback: () => void) {
|
|
15
|
-
// 1. 如果该 key 已经存在,先清除旧的定时器,防止重复触发
|
|
16
|
-
if (this.timers[key]) {
|
|
17
|
-
clearTimeout(this.timers[key]);
|
|
18
|
-
}
|
|
19
|
-
// 2. 设置新的定时器,并保存其 ID
|
|
20
|
-
this.timers[key] = setTimeout(() => {
|
|
3
|
+
export const timer = {
|
|
4
|
+
timers,
|
|
5
|
+
start(key: string, duration: number, callback: () => void): void {
|
|
6
|
+
if (timers[key]) clearTimeout(timers[key]);
|
|
7
|
+
timers[key] = setTimeout(() => {
|
|
21
8
|
callback();
|
|
22
|
-
|
|
23
|
-
delete this.timers[key];
|
|
9
|
+
delete timers[key];
|
|
24
10
|
}, duration);
|
|
25
11
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
cancel(key: string) {
|
|
32
|
-
if (this.timers[key]) {
|
|
33
|
-
clearTimeout(this.timers[key]);
|
|
34
|
-
// 从管理器中移除该记录
|
|
35
|
-
delete this.timers[key];
|
|
12
|
+
cancel(key: string): void {
|
|
13
|
+
if (timers[key]) {
|
|
14
|
+
clearTimeout(timers[key]);
|
|
15
|
+
delete timers[key];
|
|
36
16
|
}
|
|
37
17
|
},
|
|
38
18
|
};
|