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.
Files changed (52) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +418 -970
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -3
  7. package/src/components/accordion.ts +466 -346
  8. package/src/components/flow.ts +779 -1133
  9. package/src/components/form.ts +666 -509
  10. package/src/components/menu.ts +198 -328
  11. package/src/components/modal.ts +510 -747
  12. package/src/components/offcanvas.ts +273 -367
  13. package/src/components/pagination.ts +201 -375
  14. package/src/components/swiper.ts +689 -1107
  15. package/src/components/tabs.ts +467 -778
  16. package/src/components/toc.ts +191 -244
  17. package/src/core/component.ts +332 -0
  18. package/src/core/motion.ts +298 -0
  19. package/src/core/presence.ts +233 -0
  20. package/src/core/scheduler.ts +91 -0
  21. package/src/core/view.ts +31 -0
  22. package/src/css/accordion.css +94 -3
  23. package/src/css/content.css +18 -24
  24. package/src/css/drop.css +4 -1
  25. package/src/css/form.css +8 -0
  26. package/src/css/menu.css +10 -5
  27. package/src/css/modal.css +2 -1
  28. package/src/css/offcanvas.css +5 -17
  29. package/src/css/swiper.css +2 -1
  30. package/src/css/tabs.css +8 -17
  31. package/src/css/themes/default/accordion.css +0 -48
  32. package/src/css/themes/default/swiper.css +1 -1
  33. package/src/css/themes/default/tabs.css +4 -6
  34. package/src/css/toast.css +1 -17
  35. package/src/css/tooltip.css +2 -1
  36. package/src/index.ts +8 -2
  37. package/src/locales/index.ts +3 -0
  38. package/src/primitives/drop.ts +253 -262
  39. package/src/primitives/loading.ts +22 -4
  40. package/src/primitives/parabola.ts +123 -164
  41. package/src/primitives/sticky.ts +297 -0
  42. package/src/primitives/theme.ts +164 -222
  43. package/src/primitives/toast.ts +309 -284
  44. package/src/primitives/tooltip.ts +65 -66
  45. package/src/utilities/browser.ts +38 -0
  46. package/src/utilities/dom.ts +0 -54
  47. package/src/utilities/refs.ts +61 -0
  48. package/src/utilities/state.ts +73 -0
  49. package/src/utilities/timer.ts +11 -31
  50. package/src/validation/validator.ts +289 -389
  51. package/src/components/sticky.ts +0 -297
  52. package/src/core/Component.ts +0 -377
@@ -1,17 +1,18 @@
1
1
  import {
2
+ For,
2
3
  createDeepStore,
3
4
  createEffect,
4
- createRoot,
5
+ createMemo,
5
6
  flushSync,
6
7
  jsx,
7
- untrack,
8
8
  } from 'vanilla-signal';
9
9
 
10
- import Component, {
11
- type ComponentDOM,
12
- type ComponentRuntime,
13
- } from '../core/Component.ts';
10
+ import {
11
+ type FunctionalComponent,
12
+ defineComponent,
13
+ } from '../core/component.ts';
14
14
  import { icon } from '../primitives/icons.ts';
15
+ import { joinClasses } from '../utilities/class-name.ts';
15
16
  import { isPlainObject } from '../utilities/object.ts';
16
17
  import {
17
18
  type ResolveSchema,
@@ -65,7 +66,6 @@ interface PaginationState extends Record<string, unknown> {
65
66
  total: number;
66
67
  page: PaginationPage;
67
68
  count: PaginationCount;
68
- pageCount: number;
69
69
  locked: boolean;
70
70
  }
71
71
 
@@ -83,40 +83,21 @@ interface MoreItem {
83
83
  type PaginationItem = PageItem | MoreItem;
84
84
  type PageAction = 'prev' | 'next';
85
85
 
86
- interface PaginationDOM extends ComponentDOM {
87
- root: HTMLElement | null;
88
- list: HTMLElement | null;
89
- }
90
-
91
- interface PaginationRuntime extends ComponentRuntime {
92
- built: boolean;
93
- itemsKey: string;
94
- changeId: number;
95
- }
96
-
97
- interface PaginationCleanupExtras {
98
- state?: (() => void) | null;
99
- }
100
-
101
- interface PaginationSnapshot {
102
- total: number;
103
- size: number;
104
- current: number;
105
- sibling: number;
106
- boundary: number;
107
- locked: boolean;
86
+ interface PaginationActions {
87
+ go(page: number): Pagination;
108
88
  }
109
89
 
110
- const DEFAULT_PAGE: PaginationPage = {
111
- size: 10,
112
- current: 1,
113
- };
114
-
115
- const DEFAULT_COUNT: PaginationCount = {
116
- sibling: 1,
117
- boundary: 1,
90
+ export type Pagination = FunctionalComponent<
91
+ ResolvedPaginationProps,
92
+ PaginationState,
93
+ HTMLElement,
94
+ PaginationActions
95
+ > & {
96
+ readonly pageCount: number;
118
97
  };
119
98
 
99
+ const DEFAULT_PAGE: PaginationPage = { size: 10, current: 1 };
100
+ const DEFAULT_COUNT: PaginationCount = { sibling: 1, boundary: 1 };
120
101
  const DEFAULT_CLASS_NAMES: PaginationClassNames = {
121
102
  root: 'j-pagination',
122
103
  list: 'pagination',
@@ -188,6 +169,7 @@ const PAGINATION_STATE_SCHEMA = {
188
169
  total: TOTAL_RULE,
189
170
  page: PAGE_RULE,
190
171
  count: COUNT_RULE,
172
+ locked: { type: 'boolean' },
191
173
  };
192
174
 
193
175
  function clamp(value: number, min: number, max: number): number {
@@ -255,11 +237,9 @@ function resolvePageItems(
255
237
  }
256
238
 
257
239
  const pages = new Set<number>();
258
-
259
240
  for (let page = 1; page <= Math.min(boundary, pageCount); page++) {
260
241
  pages.add(page);
261
242
  }
262
-
263
243
  for (
264
244
  let page = Math.max(1, current - sibling);
265
245
  page <= Math.min(pageCount, current + sibling);
@@ -267,7 +247,6 @@ function resolvePageItems(
267
247
  ) {
268
248
  pages.add(page);
269
249
  }
270
-
271
250
  for (
272
251
  let page = Math.max(1, pageCount - boundary + 1);
273
252
  page <= pageCount;
@@ -278,404 +257,251 @@ function resolvePageItems(
278
257
 
279
258
  const sorted = Array.from(pages).sort((a, b) => a - b);
280
259
  const items: PaginationItem[] = [];
281
-
282
260
  for (const page of sorted) {
283
261
  const previous = items[items.length - 1];
284
262
  if (previous?.type === 'page' && page - previous.page > 1) {
285
- if (page - previous.page === 2) {
286
- items.push(createPageItem(previous.page + 1));
287
- } else {
288
- items.push(createMoreItem(`more-${previous.page}-${page}`));
289
- }
263
+ items.push(
264
+ page - previous.page === 2
265
+ ? createPageItem(previous.page + 1)
266
+ : createMoreItem(`more-${previous.page}-${page}`)
267
+ );
290
268
  }
291
269
  items.push(createPageItem(page));
292
270
  }
293
-
294
271
  return items;
295
272
  }
296
273
 
297
- class PaginationComponent extends Component<
298
- ResolvedPaginationProps,
299
- PaginationState,
300
- PaginationDOM
301
- > {
302
- declare runtime: PaginationRuntime;
303
- declare state: PaginationState;
304
- declare cleanup: Component['cleanup'] & PaginationCleanupExtras;
305
-
306
- constructor(input: PaginationProps = {}) {
307
- const props = normalizeProps(input);
308
- super(props);
309
-
310
- this.dom.list = null;
311
- this.cleanup.state = null;
312
-
313
- this.runtime.built = false;
314
- this.runtime.itemsKey = '';
315
- this.runtime.changeId = 0;
316
-
317
- this.state = createDeepStore({
318
- total: props.total,
319
- page: clonePage(props.page),
320
- count: cloneCount(props.count),
321
- pageCount: getPageCount(props.total, props.page.size),
322
- locked: false,
323
- }) as PaginationState;
274
+ function requirePageItem(item: PaginationItem): PageItem {
275
+ if (item.type !== 'page') {
276
+ throw new Error('Pagination.itemView: expects page item.');
324
277
  }
278
+ return item;
279
+ }
325
280
 
326
- build(): this {
327
- if (this.runtime.destroyed) {
328
- throw new Error('Pagination.build: instance has been destroyed.');
281
+ export function createPagination(input: PaginationProps = {}): Pagination {
282
+ const props = normalizeProps(input);
283
+ const state = createDeepStore({
284
+ total: props.total,
285
+ page: clonePage(props.page),
286
+ count: cloneCount(props.count),
287
+ locked: false,
288
+ }) as PaginationState;
289
+ let changeId = 0;
290
+ let pagination: Pagination;
291
+
292
+ const pageCount = createMemo(() => getPageCount(state.total, state.page.size));
293
+ const isLocked = (): boolean => props.lock && state.locked;
294
+ const isPrevDisabled = (): boolean => isLocked() || state.page.current <= 1;
295
+ const isNextDisabled = (): boolean =>
296
+ isLocked() || state.page.current >= pageCount();
297
+ const pageItems = createMemo(() =>
298
+ resolvePageItems(state.page.current, pageCount(), state.count)
299
+ );
300
+
301
+ const unlock = (id: number): void => {
302
+ if (props.lock && !pagination.runtime.destroyed && id === changeId) {
303
+ flushSync(() => {
304
+ state.locked = false;
305
+ });
329
306
  }
330
- if (this.runtime.built) return this;
331
-
332
- this.dom.root = jsx('div', {
333
- className: this.props.className.root,
334
- role: 'navigation',
335
- 'aria-label': 'Pagination',
336
- 'data-pagination': 'root',
337
- children: jsx('ul', {
338
- className: this.props.className.list,
339
- 'data-pagination-list': '',
340
- 'aria-live': 'polite',
341
- }),
342
- }) as HTMLElement;
343
-
344
- this.dom.list = this.dom.root.querySelector('[data-pagination-list]');
345
- this.runtime.built = true;
346
- this.bindState();
347
- this.bindEvents();
348
- this.emit('init', this.props);
349
- return this;
350
- }
307
+ };
351
308
 
352
- go(page: number): this {
353
- this.assertActive('go');
309
+ const go = (page: number): Pagination => {
310
+ if (pagination.runtime.destroyed) {
311
+ throw new Error('Pagination.go: instance destroyed');
312
+ }
313
+ if (!pagination.runtime.built) {
314
+ throw new Error('Pagination.go: call build() first.');
315
+ }
354
316
  validateParam(
355
317
  'page',
356
318
  page,
357
- {
358
- type: 'number',
359
- integer: true,
360
- },
319
+ { type: 'number', integer: true },
361
320
  'Pagination.go'
362
321
  );
322
+ if (isLocked()) return pagination;
363
323
 
364
- if (this.isLocked()) return this;
365
-
366
- const nextPage = clamp(page, 1, this.state.pageCount);
367
- if (nextPage === this.state.page.current) return this;
368
-
369
- const shouldLock =
370
- this.props.lock && typeof this.props.onChange === 'function';
371
- const changeId = shouldLock
372
- ? ++this.runtime.changeId
373
- : this.runtime.changeId;
324
+ const nextPage = clamp(page, 1, pageCount());
325
+ if (nextPage === state.page.current) return pagination;
326
+ const shouldLock = props.lock && typeof props.onChange === 'function';
327
+ const currentChangeId = shouldLock ? ++changeId : changeId;
374
328
 
375
329
  flushSync(() => {
376
- this.state.page.current = nextPage;
377
- if (shouldLock) this.state.locked = true;
330
+ state.page.current = nextPage;
331
+ if (shouldLock) state.locked = true;
378
332
  });
379
333
 
380
- if (typeof this.props.onChange === 'function') {
334
+ if (props.onChange) {
381
335
  let result: void | Promise<unknown>;
382
336
  try {
383
- result = this.props.onChange(nextPage, this);
337
+ result = props.onChange(nextPage, pagination);
384
338
  } catch (error) {
385
- this.unlock(changeId);
339
+ unlock(currentChangeId);
386
340
  throw error;
387
341
  }
388
342
 
389
343
  if (shouldLock && result && typeof result.then === 'function') {
390
- const unlock = () => this.unlock(changeId);
391
- void Promise.resolve(result).then(unlock, unlock);
344
+ const release = () => unlock(currentChangeId);
345
+ void Promise.resolve(result).then(release, release);
392
346
  } else {
393
- this.unlock(changeId);
347
+ unlock(currentChangeId);
394
348
  }
395
349
  }
350
+ return pagination;
351
+ };
396
352
 
397
- return this;
398
- }
399
-
400
- private bindState(): void {
401
- if (this.cleanup.state) return;
402
-
403
- this.cleanup.state = createRoot((dispose) => {
404
- createEffect(() => {
405
- const snapshot: PaginationSnapshot = {
406
- total: this.state.total,
407
- size: this.state.page.size,
408
- current: this.state.page.current,
409
- sibling: this.state.count.sibling,
410
- boundary: this.state.count.boundary,
411
- locked: this.state.locked,
412
- };
413
- untrack(() => this.syncState(snapshot));
414
- });
415
- return dispose;
416
- });
417
- }
418
-
419
- private syncState(snapshot: PaginationSnapshot): void {
420
- validateParam('total', snapshot.total, TOTAL_RULE, 'Pagination.state');
421
- validateParam(
422
- 'page',
423
- { size: snapshot.size, current: snapshot.current },
424
- PAGE_RULE,
425
- 'Pagination.state'
426
- );
427
- validateParam(
428
- 'count',
429
- { sibling: snapshot.sibling, boundary: snapshot.boundary },
430
- COUNT_RULE,
431
- 'Pagination.state'
432
- );
433
-
434
- const pageCount = getPageCount(snapshot.total, snapshot.size);
435
- const current = clamp(snapshot.current, 1, pageCount);
436
-
437
- if (
438
- this.state.pageCount !== pageCount ||
439
- this.state.page.current !== current
440
- ) {
441
- flushSync(() => {
442
- this.state.pageCount = pageCount;
443
- this.state.page.current = current;
444
- });
445
- }
446
-
447
- this.renderItems();
448
- }
449
-
450
- private renderItems(): void {
451
- if (!this.dom.list) return;
452
-
453
- const items = this.getPageItems();
454
- const itemsKey = [
455
- this.state.page.current,
456
- this.state.pageCount,
457
- this.isLocked() ? 'locked' : 'unlocked',
458
- ...items.map((item) => item.key),
459
- ].join('|');
460
-
461
- if (itemsKey === this.runtime.itemsKey) return;
462
-
463
- this.runtime.itemsKey = itemsKey;
464
- this.dom.list.textContent = '';
465
- this.dom.list.append(
466
- this.buildControlItem('prev'),
467
- ...items.map((item) => this.buildPageItem(item)),
468
- this.buildControlItem('next')
469
- );
470
- }
471
-
472
- private getPageItems(): PaginationItem[] {
473
- return resolvePageItems(this.state.page.current, this.state.pageCount, {
474
- sibling: this.state.count.sibling,
475
- boundary: this.state.count.boundary,
476
- });
477
- }
478
-
479
- private isLocked(): boolean {
480
- return this.props.lock && this.state.locked;
481
- }
482
-
483
- private isPrevDisabled(): boolean {
484
- return this.isLocked() || this.state.page.current <= 1;
485
- }
486
-
487
- private isNextDisabled(): boolean {
488
- return this.isLocked() || this.state.page.current >= this.state.pageCount;
489
- }
490
-
491
- private buildControlItem(type: PageAction): HTMLElement {
492
- const disabled =
493
- type === 'prev' ? this.isPrevDisabled() : this.isNextDisabled();
353
+ const controlView = (type: PageAction): HTMLElement => {
354
+ const disabled = type === 'prev' ? isPrevDisabled : isNextDisabled;
494
355
  return jsx('li', {
495
- className: this.props.className.item,
356
+ className: props.className.item,
496
357
  'data-pagination-control': type,
497
358
  children: jsx('button', {
498
- className: this.props.className.button,
359
+ className: props.className.button,
499
360
  type: 'button',
500
361
  'data-page-action': type,
501
362
  'aria-label':
502
363
  type === 'prev' ? 'Go to previous page' : 'Go to next page',
503
- 'aria-disabled': disabled ? 'true' : 'false',
504
- tabindex: disabled ? '-1' : null,
364
+ 'aria-disabled': () => (disabled() ? 'true' : 'false'),
365
+ tabindex: () => (disabled() ? '-1' : null),
505
366
  disabled,
367
+ onClick: () => {
368
+ if (!disabled()) {
369
+ go(state.page.current + (type === 'prev' ? -1 : 1));
370
+ }
371
+ },
506
372
  children: icon(type === 'prev' ? 'arrow-left' : 'arrow-right'),
507
373
  }),
508
374
  }) as HTMLElement;
509
- }
510
-
511
- private buildPageItem(item: PaginationItem): HTMLElement {
512
- const disabled = this.isLocked();
375
+ };
513
376
 
514
- if (item.type === 'more') {
377
+ const itemView = (itemAccessor: () => PaginationItem): HTMLElement => {
378
+ if (itemAccessor().type === 'more') {
515
379
  return jsx('li', {
516
- className: [this.props.className.item, this.props.className.more]
517
- .filter(Boolean)
518
- .join(' '),
519
- 'data-pagination-more': item.key,
380
+ className: () =>
381
+ joinClasses(
382
+ props.className.item,
383
+ itemAccessor().type === 'more' ? props.className.more : ''
384
+ ),
385
+ 'data-pagination-more': () =>
386
+ itemAccessor().type === 'more' ? itemAccessor().key : null,
520
387
  children: jsx('span', {
521
- className: this.props.className.button,
388
+ className: props.className.button,
522
389
  children: icon('more'),
523
390
  }),
524
391
  }) as HTMLElement;
525
392
  }
526
393
 
527
- if (item.page === this.state.page.current) {
528
- return jsx('li', {
529
- className: this.props.className.item,
530
- 'data-pagination-item': String(item.page),
531
- children: jsx('span', {
532
- className: this.props.className.current,
533
- 'data-current-page': String(item.page),
534
- 'aria-current': 'page',
535
- 'aria-label': `Page ${item.page}, current page`,
536
- children: disabled
537
- ? jsx('i', {
538
- className: this.props.className.loading,
539
- children: icon('loader'),
540
- })
541
- : String(item.page),
542
- }),
543
- }) as HTMLElement;
544
- }
394
+ const page = (): number => requirePageItem(itemAccessor()).page;
395
+ const isCurrentPage = (): boolean => page() === state.page.current;
545
396
 
546
397
  return jsx('li', {
547
- className: this.props.className.item,
548
- 'data-pagination-item': String(item.page),
398
+ className: () =>
399
+ joinClasses(
400
+ props.className.item,
401
+ itemAccessor().type === 'more' ? props.className.more : ''
402
+ ),
403
+ 'data-pagination-more': () =>
404
+ itemAccessor().type === 'more' ? itemAccessor().key : null,
405
+ 'data-pagination-item': () => {
406
+ const item = itemAccessor();
407
+ return item.type === 'page' ? String(item.page) : null;
408
+ },
549
409
  children: jsx('button', {
550
- className: this.props.className.button,
410
+ className: () =>
411
+ isCurrentPage() ? props.className.current : props.className.button,
551
412
  type: 'button',
552
- 'data-page': String(item.page),
553
- 'aria-label': `Go to page ${item.page}`,
554
- 'aria-disabled': disabled ? 'true' : 'false',
555
- tabindex: disabled ? '-1' : null,
556
- disabled,
557
- children: String(item.page),
413
+ 'data-page': () => String(page()),
414
+ 'data-current-page': () => (isCurrentPage() ? String(page()) : null),
415
+ 'aria-current': () => (isCurrentPage() ? 'page' : null),
416
+ 'aria-label': () =>
417
+ isCurrentPage()
418
+ ? `Page ${page()}, current page`
419
+ : `Go to page ${page()}`,
420
+ 'aria-disabled': () =>
421
+ isLocked() || isCurrentPage() ? 'true' : 'false',
422
+ tabindex: () => (isLocked() || isCurrentPage() ? '-1' : null),
423
+ disabled: () => isLocked() || isCurrentPage(),
424
+ onClick: () => {
425
+ const current = page();
426
+ if (!isLocked() && !isCurrentPage()) go(current);
427
+ },
428
+ children: () =>
429
+ isLocked() && isCurrentPage()
430
+ ? jsx('i', {
431
+ className: props.className.loading,
432
+ children: icon('loader'),
433
+ })
434
+ : String(page()),
558
435
  }),
559
436
  }) as HTMLElement;
560
- }
561
-
562
- private bindEvents(): void {
563
- this.unbindEvents();
564
- const root = this.dom.root;
565
- if (!root) return;
566
-
567
- this.cleanup.events.on('click', root, 'click', (event) => {
568
- const source = event.target;
569
- if (!(source instanceof Element)) return;
570
-
571
- const target = source.closest<HTMLElement>(
572
- '[data-page], [data-page-action]'
573
- );
574
- if (!target || !root.contains(target)) return;
575
-
576
- event.preventDefault();
577
-
578
- const action = target.dataset.pageAction;
579
- if (action === 'prev' && !this.isPrevDisabled()) {
580
- this.go(this.state.page.current - 1);
581
- return;
582
- }
437
+ };
583
438
 
584
- if (action === 'next' && !this.isNextDisabled()) {
585
- this.go(this.state.page.current + 1);
586
- return;
439
+ pagination = defineComponent<
440
+ ResolvedPaginationProps,
441
+ PaginationState,
442
+ HTMLElement,
443
+ PaginationActions
444
+ >({
445
+ name: 'Pagination',
446
+ props,
447
+ state,
448
+ actions: { go },
449
+ normalizeStatePatch(patch) {
450
+ const next = { ...patch };
451
+ if (Object.hasOwn(next, 'page') && isPlainObject(next.page)) {
452
+ next.page = { ...state.page, ...next.page } as PaginationPage;
587
453
  }
588
-
589
- if (target.dataset.page) {
590
- this.go(Number(target.dataset.page));
454
+ if (Object.hasOwn(next, 'count') && isPlainObject(next.count)) {
455
+ next.count = { ...state.count, ...next.count } as PaginationCount;
591
456
  }
592
- });
593
- }
594
-
595
- private unbindEvents(): void {
596
- this.cleanup.events.clear();
597
- }
598
-
599
- private unlock(changeId: number): void {
600
- if (
601
- this.props.lock &&
602
- !this.runtime.destroyed &&
603
- changeId === this.runtime.changeId
604
- ) {
605
- flushSync(() => {
606
- this.state.locked = false;
607
- });
608
- }
609
- }
610
-
611
- private assertActive(method: string): void {
612
- if (this.runtime.destroyed) {
613
- throw new Error(`Pagination.${method}: instance has been destroyed.`);
614
- }
615
- if (!this.runtime.built) {
616
- throw new Error(`Pagination.${method}: call build() first.`);
617
- }
618
- }
619
-
620
- protected override normalizeStatePatch(
621
- patch: Partial<PaginationState>
622
- ): Partial<PaginationState> {
623
- const nextPatch = { ...patch };
624
- if (Object.hasOwn(nextPatch, 'page') && isPlainObject(nextPatch.page)) {
625
- nextPatch.page = {
626
- ...this.state.page,
627
- ...(nextPatch.page as Partial<PaginationPage>),
628
- };
629
- }
630
- if (Object.hasOwn(nextPatch, 'count') && isPlainObject(nextPatch.count)) {
631
- nextPatch.count = {
632
- ...this.state.count,
633
- ...(nextPatch.count as Partial<PaginationCount>),
634
- };
635
- }
636
- return nextPatch;
637
- }
638
-
639
- protected override validateStatePatch(patch: Partial<PaginationState>): void {
640
- validateParam(
641
- 'state',
642
- patch,
643
- {
644
- type: 'plainObject',
645
- },
646
- 'Pagination.setState'
647
- );
648
-
649
- for (const key of Object.keys(patch)) {
650
- if (!Object.hasOwn(PAGINATION_STATE_SCHEMA, key)) {
651
- throw new Error(
652
- `Pagination.setState: "${key}" is not a supported state key.`
457
+ return next;
458
+ },
459
+ validateStatePatch(patch) {
460
+ for (const key of Object.keys(patch)) {
461
+ if (!Object.hasOwn(PAGINATION_STATE_SCHEMA, key)) {
462
+ throw new Error(`Pagination.setState: "${key}" is not supported.`);
463
+ }
464
+ const stateKey = key as keyof typeof PAGINATION_STATE_SCHEMA;
465
+ validateParam(
466
+ key,
467
+ patch[key as keyof PaginationState],
468
+ PAGINATION_STATE_SCHEMA[stateKey],
469
+ 'Pagination.setState'
653
470
  );
654
471
  }
655
- const stateKey = key as keyof typeof PAGINATION_STATE_SCHEMA;
656
- validateParam(
657
- key,
658
- patch[key as keyof PaginationState],
659
- PAGINATION_STATE_SCHEMA[stateKey],
660
- 'Pagination.setState'
661
- );
662
- }
663
- }
664
-
665
- protected onDestroy(): void {
666
- this.unbindEvents();
667
- this.cleanup.state?.();
668
- this.cleanup.state = null;
669
- this.dom.root?.remove();
670
- this.runtime.built = false;
671
- this.runtime.itemsKey = '';
672
- this.runtime.changeId = 0;
673
- this.dom.list = null;
674
- }
675
- }
676
-
677
- export type Pagination = PaginationComponent;
472
+ },
473
+ view: () => {
474
+ createEffect(() => {
475
+ const current = clamp(state.page.current, 1, pageCount());
476
+ if (current !== state.page.current) state.page.current = current;
477
+ });
478
+ return jsx('div', {
479
+ className: props.className.root,
480
+ role: 'navigation',
481
+ 'aria-label': 'Pagination',
482
+ 'data-pagination': 'root',
483
+ children: jsx('ul', {
484
+ className: props.className.list,
485
+ 'data-pagination-list': '',
486
+ 'aria-live': 'polite',
487
+ children: [
488
+ controlView('prev'),
489
+ For({
490
+ each: pageItems,
491
+ key: (item: PaginationItem) => item.key,
492
+ children: (itemAccessor: () => PaginationItem) =>
493
+ itemView(itemAccessor),
494
+ }),
495
+ controlView('next'),
496
+ ],
497
+ }),
498
+ }) as HTMLElement;
499
+ },
500
+ }) as Pagination;
678
501
 
679
- export function createPagination(input: PaginationProps = {}): Pagination {
680
- return new PaginationComponent(input);
502
+ Object.defineProperty(pagination, 'pageCount', {
503
+ enumerable: true,
504
+ get: pageCount,
505
+ });
506
+ return pagination;
681
507
  }