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
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createDeepStore,
|
|
3
3
|
createEffect,
|
|
4
|
-
createRoot,
|
|
5
4
|
flushSync,
|
|
5
|
+
insert,
|
|
6
6
|
jsx,
|
|
7
7
|
untrack,
|
|
8
8
|
} from 'vanilla-signal';
|
|
9
9
|
|
|
10
|
-
import Component, {
|
|
11
|
-
type ComponentDOM,
|
|
12
|
-
type ComponentRuntime,
|
|
13
|
-
} from '../core/Component.ts';
|
|
14
|
-
import { createLoading } from '../primitives/loading.ts';
|
|
15
10
|
import {
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
} from '../
|
|
11
|
+
type FunctionalComponent,
|
|
12
|
+
defineComponent,
|
|
13
|
+
} from '../core/component.ts';
|
|
14
|
+
import { createLoading } from '../primitives/loading.ts';
|
|
15
|
+
import { type RenderableContent } from '../utilities/dom.ts';
|
|
19
16
|
import { randomId } from '../utilities/id.ts';
|
|
20
|
-
import {
|
|
17
|
+
import { createEventManager } from '../utilities/events.ts';
|
|
18
|
+
import { createMotionGroup, createTransition } from '../core/motion.ts';
|
|
19
|
+
import { createPresence } from '../core/presence.ts';
|
|
20
|
+
import { createElementRef } from '../utilities/refs.ts';
|
|
21
21
|
import {
|
|
22
22
|
type ResolveSchema,
|
|
23
23
|
resolveProps,
|
|
@@ -79,38 +79,35 @@ interface ResolvedOffcanvasProps extends Record<string, unknown> {
|
|
|
79
79
|
|
|
80
80
|
interface OffcanvasState extends Record<string, unknown> {
|
|
81
81
|
content: OffcanvasContent;
|
|
82
|
+
resolvedContent: RenderableContent<Offcanvas>;
|
|
82
83
|
visible: boolean;
|
|
83
84
|
loading: boolean;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
interface OffcanvasDOM extends ComponentDOM {
|
|
87
|
-
root: HTMLElement | null;
|
|
88
|
-
overlay: HTMLElement | null;
|
|
89
|
-
content: HTMLElement | null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
87
|
interface OffcanvasCache {
|
|
93
88
|
content: RenderableContent<Offcanvas>;
|
|
94
89
|
hasContent: boolean;
|
|
95
90
|
updatedAt: number;
|
|
96
91
|
}
|
|
97
92
|
|
|
98
|
-
interface
|
|
99
|
-
built: boolean;
|
|
93
|
+
interface OffcanvasRuntimeExtras {
|
|
100
94
|
cache: OffcanvasCache;
|
|
101
95
|
contentLoadId: number;
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
interface OffcanvasCleanupExtras {
|
|
105
|
-
state?: (() => void) | null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
98
|
const DEFAULT_CLASS_NAMES: OffcanvasClassNames = {
|
|
109
99
|
root: 'j-offcanvas',
|
|
110
100
|
overlay: 'j-offcanvas-overlay',
|
|
111
101
|
content: 'offcanvas-content',
|
|
112
102
|
};
|
|
113
103
|
|
|
104
|
+
const OFFCANVAS_HIDDEN_TRANSFORM: Record<OffcanvasDirection, string> = {
|
|
105
|
+
top: 'translateY(-100%)',
|
|
106
|
+
right: 'translateX(100%)',
|
|
107
|
+
bottom: 'translateY(100%)',
|
|
108
|
+
left: 'translateX(-100%)',
|
|
109
|
+
};
|
|
110
|
+
|
|
114
111
|
const OFFCANVAS_PROPS_SCHEMA = {
|
|
115
112
|
content: {
|
|
116
113
|
default: '',
|
|
@@ -160,6 +157,9 @@ const OFFCANVAS_PROPS_SCHEMA = {
|
|
|
160
157
|
|
|
161
158
|
const OFFCANVAS_STATE_SCHEMA = {
|
|
162
159
|
content: OFFCANVAS_PROPS_SCHEMA.content,
|
|
160
|
+
resolvedContent: { type: 'renderable' },
|
|
161
|
+
visible: { type: 'boolean' },
|
|
162
|
+
loading: { type: 'boolean' },
|
|
163
163
|
};
|
|
164
164
|
|
|
165
165
|
function normalizeTtl(ttl: number): number {
|
|
@@ -188,389 +188,295 @@ function normalizeProps(input: OffcanvasProps): ResolvedOffcanvasProps {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
> {
|
|
196
|
-
declare runtime: OffcanvasRuntime;
|
|
197
|
-
declare state: OffcanvasState;
|
|
198
|
-
declare cleanup: Component['cleanup'] & OffcanvasCleanupExtras;
|
|
199
|
-
|
|
200
|
-
constructor(input: OffcanvasProps = {}) {
|
|
201
|
-
const props = normalizeProps(input);
|
|
202
|
-
super(props);
|
|
203
|
-
|
|
204
|
-
this.dom.overlay = null;
|
|
205
|
-
this.dom.content = null;
|
|
206
|
-
|
|
207
|
-
this.cleanup.state = null;
|
|
208
|
-
|
|
209
|
-
this.runtime.built = false;
|
|
210
|
-
this.runtime.cache = {
|
|
211
|
-
content: null,
|
|
212
|
-
hasContent: false,
|
|
213
|
-
updatedAt: 0,
|
|
214
|
-
};
|
|
215
|
-
this.runtime.contentLoadId = 0;
|
|
216
|
-
|
|
217
|
-
this.state = createDeepStore({
|
|
218
|
-
content: props.content,
|
|
219
|
-
visible: false,
|
|
220
|
-
loading: false,
|
|
221
|
-
}) as OffcanvasState;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
build(): this {
|
|
225
|
-
if (this.runtime.destroyed) {
|
|
226
|
-
throw new Error('Offcanvas.build: instance has been destroyed.');
|
|
227
|
-
}
|
|
228
|
-
if (this.runtime.built) return this;
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
this.dom.root = this.buildRoot();
|
|
232
|
-
this.runtime.built = true;
|
|
233
|
-
this.bindState();
|
|
234
|
-
} catch (error) {
|
|
235
|
-
this.destroy();
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
this.emit('init', this.props);
|
|
240
|
-
return this;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private assertBuilt(method: string): void {
|
|
244
|
-
if (this.runtime.destroyed) {
|
|
245
|
-
throw new Error(`Offcanvas.${method}: instance has been destroyed.`);
|
|
246
|
-
}
|
|
247
|
-
if (!this.runtime.built) {
|
|
248
|
-
throw new Error(`Offcanvas.${method}: call build() first.`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private buildRoot(): HTMLElement {
|
|
253
|
-
if (this.props.overlay) {
|
|
254
|
-
this.dom.overlay = this.buildOverlay();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return this.buildPanel();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private buildOverlay(): HTMLElement {
|
|
261
|
-
const filterStyle = this.props.filter
|
|
262
|
-
? { backdropFilter: 'blur(2px)' }
|
|
263
|
-
: {};
|
|
264
|
-
return jsx('div', {
|
|
265
|
-
className: this.props.className.overlay,
|
|
266
|
-
hidden: true,
|
|
267
|
-
'data-offcanvas-overlay': this.props.id,
|
|
268
|
-
style: {
|
|
269
|
-
...filterStyle,
|
|
270
|
-
},
|
|
271
|
-
}) as HTMLElement;
|
|
272
|
-
}
|
|
191
|
+
interface OffcanvasActions {
|
|
192
|
+
show(): Promise<void>;
|
|
193
|
+
hide(): Promise<void>;
|
|
194
|
+
}
|
|
273
195
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
className: this.props.className.content,
|
|
277
|
-
'data-offcanvas-content': this.props.id,
|
|
278
|
-
}) as HTMLElement;
|
|
279
|
-
this.dom.content = content;
|
|
196
|
+
let offcanvasScrollLockCount = 0;
|
|
197
|
+
let offcanvasBodyOverflow = '';
|
|
280
198
|
|
|
281
|
-
|
|
282
|
-
|
|
199
|
+
export type Offcanvas = FunctionalComponent<
|
|
200
|
+
ResolvedOffcanvasProps,
|
|
201
|
+
OffcanvasState,
|
|
202
|
+
HTMLElement,
|
|
203
|
+
OffcanvasActions
|
|
204
|
+
>;
|
|
205
|
+
|
|
206
|
+
function validateStatePatch(patch: Partial<OffcanvasState>): void {
|
|
207
|
+
validateParam('state', patch, { type: 'plainObject' }, 'Offcanvas.setState');
|
|
208
|
+
for (const key of Object.keys(patch)) {
|
|
209
|
+
if (!Object.hasOwn(OFFCANVAS_STATE_SCHEMA, key)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Offcanvas.setState: "${key}" is not a supported state key.`
|
|
212
|
+
);
|
|
283
213
|
}
|
|
284
|
-
|
|
285
|
-
return jsx('div', {
|
|
286
|
-
className: this.props.className.root,
|
|
287
|
-
id: this.props.id,
|
|
288
|
-
role: 'dialog',
|
|
289
|
-
'aria-modal': this.props.overlay ? 'true' : 'false',
|
|
290
|
-
'aria-expanded': 'false',
|
|
291
|
-
'data-offcanvas': 'root',
|
|
292
|
-
'data-direction': this.props.direction,
|
|
293
|
-
'data-animate': this.props.animate,
|
|
294
|
-
children: content,
|
|
295
|
-
}) as HTMLElement;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private bindState(): void {
|
|
299
|
-
if (this.cleanup.state) return;
|
|
300
|
-
|
|
301
|
-
this.cleanup.state = createRoot((dispose) => {
|
|
302
|
-
let initialized = false;
|
|
303
|
-
createEffect(() => {
|
|
304
|
-
const content = this.state.content;
|
|
305
|
-
if (!initialized) {
|
|
306
|
-
initialized = true;
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
void untrack(() => this.syncContent(content));
|
|
310
|
-
});
|
|
311
|
-
return dispose;
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
private async syncContent(content: OffcanvasContent): Promise<void> {
|
|
316
|
-
if (!this.runtime.built) return;
|
|
214
|
+
const stateKey = key as keyof typeof OFFCANVAS_STATE_SCHEMA;
|
|
317
215
|
validateParam(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
OFFCANVAS_STATE_SCHEMA
|
|
321
|
-
'Offcanvas.
|
|
216
|
+
key,
|
|
217
|
+
patch[key as keyof OffcanvasState],
|
|
218
|
+
OFFCANVAS_STATE_SCHEMA[stateKey],
|
|
219
|
+
'Offcanvas.setState'
|
|
322
220
|
);
|
|
323
|
-
this.runtime.cache.hasContent = false;
|
|
324
|
-
this.runtime.cache.content = null;
|
|
325
|
-
this.runtime.cache.updatedAt = 0;
|
|
326
|
-
|
|
327
|
-
if (typeof content === 'function') {
|
|
328
|
-
if (this.state.visible) await this.loadContent();
|
|
329
|
-
else this.clearContent();
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
this.runtime.contentLoadId += 1;
|
|
334
|
-
flushSync(() => {
|
|
335
|
-
this.state.loading = false;
|
|
336
|
-
});
|
|
337
|
-
this.renderContent(content);
|
|
338
221
|
}
|
|
222
|
+
}
|
|
339
223
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
224
|
+
export function createOffcanvas(input: OffcanvasProps = {}): Offcanvas {
|
|
225
|
+
const props = normalizeProps(input);
|
|
226
|
+
const state = createDeepStore({
|
|
227
|
+
content: props.content,
|
|
228
|
+
resolvedContent: typeof props.content === 'function' ? null : props.content,
|
|
229
|
+
visible: false,
|
|
230
|
+
loading: false,
|
|
231
|
+
}) as OffcanvasState;
|
|
232
|
+
const runtime: OffcanvasRuntimeExtras = {
|
|
233
|
+
cache: { content: null, hasContent: false, updatedAt: 0 },
|
|
234
|
+
contentLoadId: 0,
|
|
235
|
+
};
|
|
236
|
+
const events = createEventManager();
|
|
237
|
+
const overlayRef = createElementRef<HTMLElement>();
|
|
238
|
+
let offcanvas!: Offcanvas;
|
|
239
|
+
let scrollLocked = false;
|
|
240
|
+
|
|
241
|
+
const lockScroll = (): void => {
|
|
242
|
+
if (!props.bodyOverflow || scrollLocked) return;
|
|
243
|
+
if (offcanvasScrollLockCount === 0) {
|
|
244
|
+
offcanvasBodyOverflow = document.body.style.overflow;
|
|
245
|
+
document.body.style.overflow = 'hidden';
|
|
246
|
+
}
|
|
247
|
+
offcanvasScrollLockCount += 1;
|
|
248
|
+
scrollLocked = true;
|
|
249
|
+
};
|
|
250
|
+
const unlockScroll = (): void => {
|
|
251
|
+
if (!scrollLocked) return;
|
|
252
|
+
offcanvasScrollLockCount = Math.max(0, offcanvasScrollLockCount - 1);
|
|
253
|
+
if (offcanvasScrollLockCount === 0) {
|
|
254
|
+
document.body.style.overflow = offcanvasBodyOverflow;
|
|
255
|
+
offcanvasBodyOverflow = '';
|
|
256
|
+
}
|
|
257
|
+
scrollLocked = false;
|
|
258
|
+
};
|
|
346
259
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
260
|
+
const panelMotion = createTransition(() => offcanvas.element, {
|
|
261
|
+
keyframes: [
|
|
262
|
+
{ transform: OFFCANVAS_HIDDEN_TRANSFORM[props.direction] },
|
|
263
|
+
{ transform: 'translate(0, 0)' },
|
|
264
|
+
],
|
|
265
|
+
options: { duration: 300, easing: 'ease' },
|
|
266
|
+
});
|
|
267
|
+
const motion = createMotionGroup(
|
|
268
|
+
...(props.animate === 'slide' ? [panelMotion] : []),
|
|
269
|
+
createTransition(() => overlayRef.current, {
|
|
270
|
+
keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
271
|
+
options: { duration: 300, easing: 'ease' },
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const presence = createPresence({
|
|
276
|
+
elements: () => [overlayRef.current, offcanvas.element],
|
|
277
|
+
mount: () => {
|
|
278
|
+
const overlay = overlayRef.current;
|
|
279
|
+
if (overlay && !overlay.isConnected) insert(document.body, overlay);
|
|
280
|
+
offcanvas.mount(document.body);
|
|
281
|
+
lockScroll();
|
|
282
|
+
},
|
|
283
|
+
activate: () => {
|
|
284
|
+
state.visible = true;
|
|
285
|
+
},
|
|
286
|
+
deactivate: () => {
|
|
287
|
+
state.visible = false;
|
|
288
|
+
},
|
|
289
|
+
motion,
|
|
290
|
+
unmount: () => {
|
|
291
|
+
overlayRef.current?.remove();
|
|
292
|
+
offcanvas.unmount();
|
|
293
|
+
unlockScroll();
|
|
294
|
+
},
|
|
295
|
+
});
|
|
350
296
|
|
|
351
|
-
|
|
352
|
-
|
|
297
|
+
const clearCache = (): void => {
|
|
298
|
+
runtime.cache = { content: null, hasContent: false, updatedAt: 0 };
|
|
299
|
+
};
|
|
353
300
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
301
|
+
const isCacheValid = (): boolean => {
|
|
302
|
+
if (!props.cache || !runtime.cache.hasContent) return false;
|
|
303
|
+
const ttl = normalizeTtl(props.ttl);
|
|
304
|
+
return !ttl || Date.now() - runtime.cache.updatedAt <= ttl;
|
|
305
|
+
};
|
|
357
306
|
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const content = this.state.content;
|
|
307
|
+
const loadContent = async (): Promise<void> => {
|
|
308
|
+
const content = state.content;
|
|
361
309
|
if (typeof content !== 'function') {
|
|
362
|
-
|
|
310
|
+
runtime.contentLoadId += 1;
|
|
363
311
|
flushSync(() => {
|
|
364
|
-
|
|
312
|
+
state.resolvedContent = content;
|
|
313
|
+
state.loading = false;
|
|
365
314
|
});
|
|
366
315
|
return;
|
|
367
316
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.runtime.contentLoadId += 1;
|
|
371
|
-
this.renderContent(this.runtime.cache.content);
|
|
317
|
+
if (isCacheValid()) {
|
|
318
|
+
runtime.contentLoadId += 1;
|
|
372
319
|
flushSync(() => {
|
|
373
|
-
|
|
320
|
+
state.resolvedContent = runtime.cache.content;
|
|
321
|
+
state.loading = false;
|
|
374
322
|
});
|
|
375
323
|
return;
|
|
376
324
|
}
|
|
377
325
|
|
|
378
|
-
const loadId = ++
|
|
326
|
+
const loadId = ++runtime.contentLoadId;
|
|
379
327
|
flushSync(() => {
|
|
380
|
-
|
|
328
|
+
state.loading = true;
|
|
329
|
+
state.resolvedContent = null;
|
|
381
330
|
});
|
|
382
|
-
|
|
383
|
-
if (this.dom.content) {
|
|
384
|
-
this.clearContent();
|
|
385
|
-
this.dom.content.appendChild(createLoading());
|
|
386
|
-
}
|
|
387
|
-
|
|
388
331
|
try {
|
|
389
|
-
const result = await Promise.resolve(content(
|
|
390
|
-
|
|
391
|
-
if (this.runtime.destroyed || loadId !== this.runtime.contentLoadId) {
|
|
332
|
+
const result = await Promise.resolve(content(offcanvas));
|
|
333
|
+
if (offcanvas.runtime.destroyed || loadId !== runtime.contentLoadId)
|
|
392
334
|
return;
|
|
335
|
+
if (props.cache) {
|
|
336
|
+
runtime.cache = {
|
|
337
|
+
content: result,
|
|
338
|
+
hasContent: true,
|
|
339
|
+
updatedAt: Date.now(),
|
|
340
|
+
};
|
|
393
341
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.runtime.cache.hasContent = true;
|
|
398
|
-
this.runtime.cache.updatedAt = Date.now();
|
|
399
|
-
}
|
|
400
|
-
this.renderContent(result);
|
|
342
|
+
flushSync(() => {
|
|
343
|
+
state.resolvedContent = result;
|
|
344
|
+
});
|
|
401
345
|
} finally {
|
|
402
|
-
if (!
|
|
346
|
+
if (!offcanvas.runtime.destroyed && loadId === runtime.contentLoadId) {
|
|
403
347
|
flushSync(() => {
|
|
404
|
-
|
|
348
|
+
state.loading = false;
|
|
405
349
|
});
|
|
406
350
|
}
|
|
407
351
|
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private bindEvents(): void {
|
|
411
|
-
this.unbindEvents();
|
|
412
|
-
const { bgClose, escClose } = this.props;
|
|
413
|
-
|
|
414
|
-
if (this.dom.overlay && bgClose) {
|
|
415
|
-
this.cleanup.events.on('overlay', this.dom.overlay, 'click', () => {
|
|
416
|
-
void this.hide();
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
if (escClose) {
|
|
420
|
-
this.cleanup.events.on('esc', document, 'keydown', (event) => {
|
|
421
|
-
if (
|
|
422
|
-
event instanceof KeyboardEvent &&
|
|
423
|
-
event.key === 'Escape' &&
|
|
424
|
-
this.state.visible
|
|
425
|
-
) {
|
|
426
|
-
void this.hide();
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
if (this.dom.root) {
|
|
431
|
-
this.cleanup.events.on('close', this.dom.root, 'click', (event) => {
|
|
432
|
-
if (!(event.target instanceof Element)) return;
|
|
433
|
-
const action = event.target.closest<HTMLElement>('[data-action]');
|
|
434
|
-
const name = action?.dataset.action;
|
|
435
|
-
if (name === 'close' || name === 'cancel') void this.hide();
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
private unbindEvents(): void {
|
|
441
|
-
this.cleanup.events.clear();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private showPanel(): void {
|
|
445
|
-
const { id } = this.props;
|
|
446
|
-
timer.cancel(`oc-hide-${id}`);
|
|
447
|
-
|
|
448
|
-
if (this.dom.overlay && !this.dom.overlay.isConnected) {
|
|
449
|
-
this.dom.overlay.hidden = false;
|
|
450
|
-
document.body.appendChild(this.dom.overlay);
|
|
451
|
-
}
|
|
452
|
-
if (this.dom.root && !this.dom.root.isConnected) {
|
|
453
|
-
document.body.appendChild(this.dom.root);
|
|
454
|
-
}
|
|
455
|
-
if (this.props.bodyOverflow) {
|
|
456
|
-
document.body.style.overflow = 'hidden';
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
timer.start(`oc-show-${id}`, 10, () => {
|
|
460
|
-
this.dom.root?.setAttribute('aria-expanded', 'true');
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
private hidePanel(): void {
|
|
465
|
-
const { id } = this.props;
|
|
466
|
-
timer.cancel(`oc-show-${id}`);
|
|
467
|
-
timer.cancel(`oc-shown-${id}`);
|
|
468
|
-
|
|
469
|
-
this.dom.root?.setAttribute('aria-expanded', 'false');
|
|
352
|
+
};
|
|
470
353
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
document.body.style.overflow = '';
|
|
354
|
+
const actions: OffcanvasActions = {
|
|
355
|
+
async show() {
|
|
356
|
+
if (!offcanvas.runtime.built) {
|
|
357
|
+
throw new Error('Offcanvas.show: call build() first.');
|
|
476
358
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
async show(): Promise<void> {
|
|
481
|
-
this.assertBuilt('show');
|
|
482
|
-
if (this.state.visible) return;
|
|
483
|
-
|
|
484
|
-
const { onShow, onShown, id } = this.props;
|
|
485
|
-
if (onShow) await Promise.resolve(onShow(this));
|
|
359
|
+
if (state.visible) return;
|
|
360
|
+
await Promise.resolve(props.onShow?.(offcanvas));
|
|
486
361
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
async hide(): Promise<void> {
|
|
503
|
-
this.assertBuilt('hide');
|
|
504
|
-
if (!this.state.visible) return;
|
|
505
|
-
|
|
506
|
-
const { onHide, onHidden } = this.props;
|
|
507
|
-
if (onHide) await Promise.resolve(onHide(this));
|
|
508
|
-
|
|
509
|
-
this.runtime.contentLoadId += 1;
|
|
510
|
-
flushSync(() => {
|
|
511
|
-
this.state.loading = false;
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
this.unbindEvents();
|
|
515
|
-
this.hidePanel();
|
|
516
|
-
|
|
517
|
-
flushSync(() => {
|
|
518
|
-
this.state.visible = false;
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
void onHidden?.(this);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
protected onDestroy(): void {
|
|
525
|
-
const { id } = this.props;
|
|
526
|
-
|
|
527
|
-
timer.cancel(`oc-show-${id}`);
|
|
528
|
-
timer.cancel(`oc-hide-${id}`);
|
|
529
|
-
timer.cancel(`oc-shown-${id}`);
|
|
530
|
-
|
|
531
|
-
this.unbindEvents();
|
|
532
|
-
this.cleanup.state?.();
|
|
533
|
-
this.cleanup.state = null;
|
|
534
|
-
|
|
535
|
-
this.dom.root?.setAttribute('aria-expanded', 'false');
|
|
536
|
-
this.dom.overlay?.remove();
|
|
537
|
-
this.dom.root?.remove();
|
|
538
|
-
if (this.props.bodyOverflow) {
|
|
539
|
-
document.body.style.overflow = '';
|
|
540
|
-
}
|
|
362
|
+
const entering = presence.enter();
|
|
363
|
+
const loading = loadContent();
|
|
364
|
+
if ((await entering) && !offcanvas.runtime.destroyed) {
|
|
365
|
+
void props.onShown?.(offcanvas);
|
|
366
|
+
}
|
|
367
|
+
await loading;
|
|
368
|
+
},
|
|
369
|
+
async hide() {
|
|
370
|
+
if (!offcanvas.runtime.built) {
|
|
371
|
+
throw new Error('Offcanvas.hide: call build() first.');
|
|
372
|
+
}
|
|
373
|
+
if (!state.visible) return;
|
|
374
|
+
const onHideResult = props.onHide?.(offcanvas);
|
|
375
|
+
if (onHideResult) await onHideResult;
|
|
541
376
|
|
|
542
|
-
|
|
543
|
-
|
|
377
|
+
runtime.contentLoadId += 1;
|
|
378
|
+
flushSync(() => {
|
|
379
|
+
state.loading = false;
|
|
380
|
+
});
|
|
381
|
+
if (await presence.leave()) {
|
|
382
|
+
void props.onHidden?.(offcanvas);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
};
|
|
544
386
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
387
|
+
offcanvas = defineComponent({
|
|
388
|
+
name: 'Offcanvas',
|
|
389
|
+
props,
|
|
390
|
+
state,
|
|
391
|
+
actions,
|
|
392
|
+
validateStatePatch,
|
|
393
|
+
view(context) {
|
|
394
|
+
const overlay = props.overlay
|
|
395
|
+
? (jsx('div', {
|
|
396
|
+
ref: overlayRef.set,
|
|
397
|
+
className: props.className.overlay,
|
|
398
|
+
'data-offcanvas-overlay': props.id,
|
|
399
|
+
'data-mount': () => (state.visible ? 'true' : 'false'),
|
|
400
|
+
style: props.filter ? { backdropFilter: 'blur(2px)' } : {},
|
|
401
|
+
onClick: () => {
|
|
402
|
+
if (props.bgClose) void offcanvas.hide();
|
|
403
|
+
},
|
|
404
|
+
}) as HTMLElement)
|
|
405
|
+
: null;
|
|
406
|
+
if (overlay) context.own(() => overlay.remove());
|
|
554
407
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
408
|
+
createEffect(() => {
|
|
409
|
+
const content = state.content;
|
|
410
|
+
validateParam(
|
|
411
|
+
'content',
|
|
412
|
+
content,
|
|
413
|
+
OFFCANVAS_STATE_SCHEMA.content,
|
|
414
|
+
'Offcanvas.state'
|
|
559
415
|
);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
416
|
+
clearCache();
|
|
417
|
+
if (typeof content === 'function') {
|
|
418
|
+
state.resolvedContent = null;
|
|
419
|
+
if (untrack(() => state.visible)) void loadContent();
|
|
420
|
+
} else {
|
|
421
|
+
runtime.contentLoadId += 1;
|
|
422
|
+
state.resolvedContent = content;
|
|
423
|
+
state.loading = false;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
571
426
|
|
|
572
|
-
|
|
427
|
+
return jsx('div', {
|
|
428
|
+
className: props.className.root,
|
|
429
|
+
id: props.id,
|
|
430
|
+
role: 'dialog',
|
|
431
|
+
'aria-modal': props.overlay ? 'true' : 'false',
|
|
432
|
+
'aria-expanded': () => String(state.visible),
|
|
433
|
+
'data-mount': () => (state.visible ? 'true' : 'false'),
|
|
434
|
+
'data-offcanvas': 'root',
|
|
435
|
+
'data-direction': props.direction,
|
|
436
|
+
'data-animate': props.animate,
|
|
437
|
+
onClick: (event: Event) => {
|
|
438
|
+
if (!(event.target instanceof Element)) return;
|
|
439
|
+
const action = event.target.closest<HTMLElement>('[data-action]');
|
|
440
|
+
if (
|
|
441
|
+
action?.dataset.action === 'close' ||
|
|
442
|
+
action?.dataset.action === 'cancel'
|
|
443
|
+
) {
|
|
444
|
+
void offcanvas.hide();
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
children: jsx('div', {
|
|
448
|
+
className: props.className.content,
|
|
449
|
+
'data-offcanvas-content': props.id,
|
|
450
|
+
children: () =>
|
|
451
|
+
state.loading
|
|
452
|
+
? createLoading()
|
|
453
|
+
: typeof state.resolvedContent === 'function'
|
|
454
|
+
? state.resolvedContent(offcanvas)
|
|
455
|
+
: state.resolvedContent,
|
|
456
|
+
}),
|
|
457
|
+
}) as HTMLElement;
|
|
458
|
+
},
|
|
459
|
+
onBuild(context) {
|
|
460
|
+
if (props.escClose) {
|
|
461
|
+
events.on('esc', document, 'keydown', (event) => {
|
|
462
|
+
if (
|
|
463
|
+
event instanceof KeyboardEvent &&
|
|
464
|
+
event.key === 'Escape' &&
|
|
465
|
+
state.visible
|
|
466
|
+
) {
|
|
467
|
+
void offcanvas.hide();
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
context.own(() => events.clear());
|
|
472
|
+
},
|
|
473
|
+
onDestroy() {
|
|
474
|
+
presence.cancel();
|
|
475
|
+
runtime.contentLoadId += 1;
|
|
476
|
+
overlayRef.current?.remove();
|
|
477
|
+
unlockScroll();
|
|
478
|
+
},
|
|
479
|
+
});
|
|
573
480
|
|
|
574
|
-
|
|
575
|
-
return new OffcanvasComponent(input);
|
|
481
|
+
return offcanvas;
|
|
576
482
|
}
|