vanilla-jui 1.4.8 → 1.5.2

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