vanilla-jui 1.4.4 → 1.4.6
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 +6 -0
- package/dist/index.d.ts +1273 -1896
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +22 -3
- package/src/components/accordion.ts +297 -216
- package/src/components/flow.ts +222 -258
- package/src/components/form.ts +44 -102
- package/src/components/menu.ts +268 -356
- package/src/components/modal.ts +181 -641
- package/src/components/offcanvas.ts +225 -106
- package/src/components/pagination.ts +296 -326
- package/src/components/sticky.ts +55 -54
- package/src/components/swiper.ts +237 -247
- package/src/components/tabs.ts +278 -271
- package/src/components/toc.ts +42 -46
- package/src/core/Component.ts +69 -47
- package/src/css/accordion.css +2 -2
- package/src/css/animation.css +1 -5
- package/src/css/avatar.css +1 -1
- package/src/css/badge.css +1 -1
- package/src/css/breadcrumb.css +9 -10
- package/src/css/button.css +1 -1
- package/src/css/card.css +2 -2
- package/src/css/color.css +974 -1002
- package/src/css/color_p3.css +974 -1004
- package/src/css/components.css +1 -1
- package/src/css/content.css +72 -63
- package/src/css/drop.css +1 -1
- package/src/css/editor.css +14 -14
- package/src/css/flow.css +4 -14
- package/src/css/font.css +5 -4
- package/src/css/form.css +76 -52
- package/src/css/gradient.css +299 -222
- package/src/css/icon.css +57 -57
- package/src/css/index.css +1 -1
- package/src/css/layout.css +13 -10
- package/src/css/loading.css +60 -12
- package/src/css/menu.css +262 -188
- package/src/css/modal.css +1 -1
- package/src/css/offcanvas.css +9 -13
- package/src/css/pagination.css +5 -1
- package/src/css/popup.css +1 -1
- package/src/css/skeleton.css +3 -2
- package/src/css/status.css +2 -3
- package/src/css/swiper.css +1 -1
- package/src/css/table.css +1 -1
- package/src/css/tabs.css +12 -11
- package/src/css/tag.css +1 -1
- package/src/css/theme-palette.css +19 -5
- package/src/css/themes/default/accordion.css +61 -61
- package/src/css/themes/default/avatar.css +54 -54
- package/src/css/themes/default/badge.css +62 -62
- package/src/css/themes/default/breadcrumb.css +23 -23
- package/src/css/themes/default/button.css +203 -203
- package/src/css/themes/default/content.css +53 -53
- package/src/css/themes/default/drop.css +10 -10
- package/src/css/themes/default/editor.css +9 -9
- package/src/css/themes/default/index.css +1 -1
- package/src/css/themes/default/loading.css +26 -20
- package/src/css/themes/default/menu.css +75 -11
- package/src/css/themes/default/modal.css +32 -36
- package/src/css/themes/default/pagination.css +7 -7
- package/src/css/themes/default/popup.css +6 -6
- package/src/css/themes/default/swiper.css +32 -32
- package/src/css/themes/default/table.css +16 -16
- package/src/css/themes/default/tabs.css +30 -30
- package/src/css/themes/default/tag.css +71 -71
- package/src/css/themes/default/theme-palette.css +11 -11
- package/src/css/themes/default/tip.css +42 -42
- package/src/css/themes/default/toast.css +60 -60
- package/src/css/themes/default/toolbar.css +23 -29
- package/src/css/themes/default/tooltip.css +43 -13
- package/src/css/themes/index.css +1 -1
- package/src/css/tip.css +2 -2
- package/src/css/toast.css +3 -3
- package/src/css/tokens.css +12 -17
- package/src/css/toolbar.css +9 -10
- package/src/css/tooltip.css +3 -2
- package/src/index.ts +22 -19
- package/src/{components → primitives}/drop.ts +58 -49
- package/src/{components → primitives}/icons.ts +1 -1
- package/src/primitives/loading.ts +23 -0
- package/src/primitives/parabola.ts +388 -0
- package/src/primitives/popup.ts +61 -0
- package/src/{components → primitives}/theme.ts +88 -57
- package/src/{components → primitives}/toast.ts +18 -25
- package/src/{components → primitives}/tooltip.ts +100 -25
- package/src/utilities/browser.ts +23 -49
- package/src/utilities/class-name.ts +5 -0
- package/src/utilities/dom.ts +14 -32
- package/src/utilities/function.ts +4 -0
- package/src/utilities/http.ts +7 -7
- package/src/utilities/id.ts +57 -0
- package/src/utilities/object.ts +5 -0
- package/src/utilities/timer.ts +38 -0
- package/src/utilities/types.ts +420 -0
- package/src/{components → validation}/validator.ts +64 -53
- package/src/components/parabola.ts +0 -335
- package/src/utilities/cache.ts +0 -57
- package/src/utilities/core.ts +0 -614
- package/src/utilities/storage.ts +0 -46
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
-
bindAttr,
|
|
3
2
|
createDeepStore,
|
|
4
3
|
createEffect,
|
|
5
4
|
createRoot,
|
|
6
5
|
flushSync,
|
|
7
6
|
jsx,
|
|
7
|
+
untrack,
|
|
8
8
|
} from 'vanilla-signal';
|
|
9
9
|
|
|
10
10
|
import Component, {
|
|
11
11
|
type ComponentDOM,
|
|
12
12
|
type ComponentRuntime,
|
|
13
13
|
} from '../core/Component.ts';
|
|
14
|
+
import { icon } from '../primitives/icons.ts';
|
|
15
|
+
import { isPlainObject } from '../utilities/object.ts';
|
|
14
16
|
import {
|
|
15
17
|
type ResolveSchema,
|
|
16
|
-
hasOwn,
|
|
17
18
|
resolveProps,
|
|
18
19
|
validateParam,
|
|
19
|
-
} from '../utilities/
|
|
20
|
-
import { type DOMReference, q, requireContainer } from '../utilities/dom.ts';
|
|
21
|
-
import { icon } from './icons.ts';
|
|
20
|
+
} from '../utilities/types.ts';
|
|
22
21
|
|
|
23
22
|
export interface PaginationPage {
|
|
24
23
|
size: number;
|
|
@@ -36,9 +35,7 @@ export interface PaginationClassNames {
|
|
|
36
35
|
item: string;
|
|
37
36
|
more: string;
|
|
38
37
|
button: string;
|
|
39
|
-
|
|
40
|
-
buttonGhost: string;
|
|
41
|
-
active: string;
|
|
38
|
+
current: string;
|
|
42
39
|
loading: string;
|
|
43
40
|
}
|
|
44
41
|
|
|
@@ -85,17 +82,10 @@ interface MoreItem {
|
|
|
85
82
|
|
|
86
83
|
type PaginationItem = PageItem | MoreItem;
|
|
87
84
|
type PageAction = 'prev' | 'next';
|
|
88
|
-
type Dispose = () => void;
|
|
89
|
-
type EffectDispose = { dispose: () => void };
|
|
90
85
|
|
|
91
86
|
interface PaginationDOM extends ComponentDOM {
|
|
92
87
|
root: HTMLElement | null;
|
|
93
|
-
container: Element | null;
|
|
94
88
|
list: HTMLElement | null;
|
|
95
|
-
prev: HTMLElement | null;
|
|
96
|
-
next: HTMLElement | null;
|
|
97
|
-
pageNodes: HTMLElement[];
|
|
98
|
-
items: PaginationItem[];
|
|
99
89
|
}
|
|
100
90
|
|
|
101
91
|
interface PaginationRuntime extends ComponentRuntime {
|
|
@@ -105,70 +95,81 @@ interface PaginationRuntime extends ComponentRuntime {
|
|
|
105
95
|
}
|
|
106
96
|
|
|
107
97
|
interface PaginationCleanupExtras {
|
|
108
|
-
|
|
109
|
-
itemsEffect?: EffectDispose | null;
|
|
98
|
+
state?: (() => void) | null;
|
|
110
99
|
}
|
|
111
100
|
|
|
101
|
+
interface PaginationSnapshot {
|
|
102
|
+
total: number;
|
|
103
|
+
size: number;
|
|
104
|
+
current: number;
|
|
105
|
+
sibling: number;
|
|
106
|
+
boundary: number;
|
|
107
|
+
locked: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const DEFAULT_PAGE: PaginationPage = {
|
|
111
|
+
size: 10,
|
|
112
|
+
current: 1,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const DEFAULT_COUNT: PaginationCount = {
|
|
116
|
+
sibling: 1,
|
|
117
|
+
boundary: 1,
|
|
118
|
+
};
|
|
119
|
+
|
|
112
120
|
const DEFAULT_CLASS_NAMES: PaginationClassNames = {
|
|
113
121
|
root: 'j-pagination',
|
|
114
122
|
list: 'pagination',
|
|
115
123
|
item: 'item',
|
|
116
124
|
more: 'more',
|
|
117
|
-
button: 'j-button',
|
|
118
|
-
|
|
119
|
-
buttonGhost: 'is-ghost',
|
|
120
|
-
active: 'is-active',
|
|
125
|
+
button: 'j-button is-icon is-ghost',
|
|
126
|
+
current: 'j-button is-icon is-active',
|
|
121
127
|
loading: 'animate-spin',
|
|
122
128
|
};
|
|
123
129
|
|
|
130
|
+
const TOTAL_RULE = {
|
|
131
|
+
default: 0,
|
|
132
|
+
type: 'number',
|
|
133
|
+
finite: true,
|
|
134
|
+
min: 0,
|
|
135
|
+
};
|
|
136
|
+
|
|
124
137
|
const PAGE_RULE = {
|
|
125
|
-
type: '
|
|
138
|
+
type: 'plainObject',
|
|
126
139
|
required: true,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
!!page &&
|
|
131
|
-
Number.isInteger(page.size) &&
|
|
132
|
-
Number(page.size) > 0 &&
|
|
133
|
-
Number.isInteger(page.current) &&
|
|
134
|
-
Number(page.current) > 0
|
|
135
|
-
);
|
|
140
|
+
shape: {
|
|
141
|
+
size: { type: 'number', integer: true, greaterThan: 0 },
|
|
142
|
+
current: { type: 'number', integer: true, greaterThan: 0 },
|
|
136
143
|
},
|
|
137
|
-
message: 'expects { size, current } with positive integers.',
|
|
138
144
|
};
|
|
139
145
|
|
|
140
146
|
const COUNT_RULE = {
|
|
141
|
-
type: '
|
|
147
|
+
type: 'plainObject',
|
|
142
148
|
required: true,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
!!count &&
|
|
147
|
-
Number.isInteger(count.sibling) &&
|
|
148
|
-
Number(count.sibling) >= 0 &&
|
|
149
|
-
Number.isInteger(count.boundary) &&
|
|
150
|
-
Number(count.boundary) >= 0
|
|
151
|
-
);
|
|
149
|
+
shape: {
|
|
150
|
+
sibling: { type: 'number', integer: true, min: 0 },
|
|
151
|
+
boundary: { type: 'number', integer: true, min: 0 },
|
|
152
152
|
},
|
|
153
|
-
message:
|
|
154
|
-
'expects { sibling, boundary } with integers greater than or equal 0.',
|
|
155
153
|
};
|
|
156
154
|
|
|
157
155
|
const PAGINATION_PROPS_SCHEMA = {
|
|
158
|
-
total:
|
|
159
|
-
default: 0,
|
|
160
|
-
type: 'number',
|
|
161
|
-
validate: (value: unknown) => Number.isFinite(value) && Number(value) >= 0,
|
|
162
|
-
message: 'expects a non-negative finite number.',
|
|
163
|
-
},
|
|
156
|
+
total: TOTAL_RULE,
|
|
164
157
|
page: {
|
|
165
|
-
default: () => ({
|
|
158
|
+
default: () => ({ ...DEFAULT_PAGE }),
|
|
166
159
|
factory: true,
|
|
160
|
+
normalize: (value: unknown) => ({
|
|
161
|
+
...DEFAULT_PAGE,
|
|
162
|
+
...(value && typeof value === 'object' ? value : {}),
|
|
163
|
+
}),
|
|
167
164
|
...PAGE_RULE,
|
|
168
165
|
},
|
|
169
166
|
count: {
|
|
170
|
-
default: () => ({
|
|
167
|
+
default: () => ({ ...DEFAULT_COUNT }),
|
|
171
168
|
factory: true,
|
|
169
|
+
normalize: (value: unknown) => ({
|
|
170
|
+
...DEFAULT_COUNT,
|
|
171
|
+
...(value && typeof value === 'object' ? value : {}),
|
|
172
|
+
}),
|
|
172
173
|
...COUNT_RULE,
|
|
173
174
|
},
|
|
174
175
|
lock: { default: true, type: 'boolean' },
|
|
@@ -183,20 +184,43 @@ const PAGINATION_PROPS_SCHEMA = {
|
|
|
183
184
|
},
|
|
184
185
|
} satisfies ResolveSchema<PaginationProps>;
|
|
185
186
|
|
|
187
|
+
const PAGINATION_STATE_SCHEMA = {
|
|
188
|
+
total: TOTAL_RULE,
|
|
189
|
+
page: PAGE_RULE,
|
|
190
|
+
count: COUNT_RULE,
|
|
191
|
+
};
|
|
192
|
+
|
|
186
193
|
function clamp(value: number, min: number, max: number): number {
|
|
187
194
|
return Math.min(Math.max(value, min), max);
|
|
188
195
|
}
|
|
189
196
|
|
|
197
|
+
function getPageCount(total: number, size: number): number {
|
|
198
|
+
return Math.max(1, Math.ceil(total / size));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function clonePage(page: PaginationPage): PaginationPage {
|
|
202
|
+
return { size: page.size, current: page.current };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function cloneCount(count: PaginationCount): PaginationCount {
|
|
206
|
+
return { sibling: count.sibling, boundary: count.boundary };
|
|
207
|
+
}
|
|
208
|
+
|
|
190
209
|
function normalizeProps(input: PaginationProps = {}): ResolvedPaginationProps {
|
|
191
210
|
const props = resolveProps(
|
|
192
211
|
input,
|
|
193
212
|
PAGINATION_PROPS_SCHEMA,
|
|
194
213
|
'Pagination.props'
|
|
195
214
|
) as ResolvedPaginationProps;
|
|
215
|
+
const pageCount = getPageCount(props.total, props.page.size);
|
|
216
|
+
|
|
196
217
|
return {
|
|
197
218
|
...props,
|
|
198
|
-
page: {
|
|
199
|
-
|
|
219
|
+
page: {
|
|
220
|
+
size: props.page.size,
|
|
221
|
+
current: clamp(props.page.current, 1, pageCount),
|
|
222
|
+
},
|
|
223
|
+
count: cloneCount(props.count),
|
|
200
224
|
className: { ...props.className },
|
|
201
225
|
};
|
|
202
226
|
}
|
|
@@ -270,36 +294,21 @@ function resolvePageItems(
|
|
|
270
294
|
return items;
|
|
271
295
|
}
|
|
272
296
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
export class Pagination extends Component {
|
|
279
|
-
declare props: ResolvedPaginationProps;
|
|
280
|
-
declare state: PaginationState;
|
|
281
|
-
declare dom: PaginationDOM;
|
|
297
|
+
class PaginationComponent extends Component<
|
|
298
|
+
ResolvedPaginationProps,
|
|
299
|
+
PaginationState,
|
|
300
|
+
PaginationDOM
|
|
301
|
+
> {
|
|
282
302
|
declare runtime: PaginationRuntime;
|
|
303
|
+
declare state: PaginationState;
|
|
283
304
|
declare cleanup: Component['cleanup'] & PaginationCleanupExtras;
|
|
284
305
|
|
|
285
|
-
|
|
286
|
-
* @param {Element|Node|string|Array} container 挂载容器。
|
|
287
|
-
* @param {object} [input={}] 分页配置。
|
|
288
|
-
*/
|
|
289
|
-
constructor(container: DOMReference, input: PaginationProps = {}) {
|
|
290
|
-
const el = requireContainer(container, 'Pagination.container');
|
|
306
|
+
constructor(input: PaginationProps = {}) {
|
|
291
307
|
const props = normalizeProps(input);
|
|
292
308
|
super(props);
|
|
293
309
|
|
|
294
|
-
const pageCount = this._getPageCount(props.total, props.page.size);
|
|
295
|
-
const current = clamp(props.page.current, 1, pageCount);
|
|
296
|
-
|
|
297
|
-
this.dom.container = el;
|
|
298
310
|
this.dom.list = null;
|
|
299
|
-
this.
|
|
300
|
-
this.dom.next = null;
|
|
301
|
-
this.dom.pageNodes = [];
|
|
302
|
-
this.dom.items = [];
|
|
311
|
+
this.cleanup.state = null;
|
|
303
312
|
|
|
304
313
|
this.runtime.built = false;
|
|
305
314
|
this.runtime.itemsKey = '';
|
|
@@ -307,31 +316,20 @@ export class Pagination extends Component {
|
|
|
307
316
|
|
|
308
317
|
this.state = createDeepStore({
|
|
309
318
|
total: props.total,
|
|
310
|
-
page:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
},
|
|
314
|
-
count: {
|
|
315
|
-
sibling: props.count.sibling,
|
|
316
|
-
boundary: props.count.boundary,
|
|
317
|
-
},
|
|
318
|
-
pageCount,
|
|
319
|
+
page: clonePage(props.page),
|
|
320
|
+
count: cloneCount(props.count),
|
|
321
|
+
pageCount: getPageCount(props.total, props.page.size),
|
|
319
322
|
locked: false,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
this.props.page.current = current;
|
|
323
|
+
}) as PaginationState;
|
|
323
324
|
}
|
|
324
325
|
|
|
325
|
-
/**
|
|
326
|
-
* 构建分页 DOM 并绑定事件。
|
|
327
|
-
* @returns {Pagination} 当前实例。
|
|
328
|
-
*/
|
|
329
326
|
build(): this {
|
|
330
|
-
this.
|
|
327
|
+
if (this.runtime.destroyed) {
|
|
328
|
+
throw new Error('Pagination.build: instance has been destroyed.');
|
|
329
|
+
}
|
|
331
330
|
if (this.runtime.built) return this;
|
|
332
331
|
|
|
333
|
-
this.
|
|
334
|
-
this.root = jsx('div', {
|
|
332
|
+
this.dom.root = jsx('div', {
|
|
335
333
|
className: this.props.className.root,
|
|
336
334
|
role: 'navigation',
|
|
337
335
|
'aria-label': 'Pagination',
|
|
@@ -343,42 +341,27 @@ export class Pagination extends Component {
|
|
|
343
341
|
}),
|
|
344
342
|
}) as HTMLElement;
|
|
345
343
|
|
|
346
|
-
this.dom.list =
|
|
347
|
-
this.dom.prev = this._buildControlItem('prev');
|
|
348
|
-
this.dom.next = this._buildControlItem('next');
|
|
349
|
-
this.dom.list?.append(this.dom.prev, this.dom.next);
|
|
350
|
-
|
|
351
|
-
const container = this.dom.container;
|
|
352
|
-
if (!container) return this;
|
|
353
|
-
container.textContent = '';
|
|
354
|
-
container.appendChild(this.root);
|
|
344
|
+
this.dom.list = this.dom.root.querySelector('[data-pagination-list]');
|
|
355
345
|
this.runtime.built = true;
|
|
356
|
-
|
|
357
|
-
this.
|
|
358
|
-
this.
|
|
359
|
-
this._bindEvents();
|
|
346
|
+
this.bindState();
|
|
347
|
+
this.bindEvents();
|
|
348
|
+
this.emit('init', this.props);
|
|
360
349
|
return this;
|
|
361
350
|
}
|
|
362
351
|
|
|
363
|
-
/**
|
|
364
|
-
* 跳转到指定页码。
|
|
365
|
-
* @param {number} page 新页码。
|
|
366
|
-
* @returns {Pagination} 当前实例。
|
|
367
|
-
*/
|
|
368
352
|
go(page: number): this {
|
|
369
|
-
this.
|
|
353
|
+
this.assertActive('go');
|
|
370
354
|
validateParam(
|
|
371
355
|
'page',
|
|
372
356
|
page,
|
|
373
357
|
{
|
|
374
358
|
type: 'number',
|
|
375
|
-
|
|
376
|
-
message: 'expects an integer.',
|
|
359
|
+
integer: true,
|
|
377
360
|
},
|
|
378
361
|
'Pagination.go'
|
|
379
362
|
);
|
|
380
363
|
|
|
381
|
-
if (this.
|
|
364
|
+
if (this.isLocked()) return this;
|
|
382
365
|
|
|
383
366
|
const nextPage = clamp(page, 1, this.state.pageCount);
|
|
384
367
|
if (nextPage === this.state.page.current) return this;
|
|
@@ -393,146 +376,140 @@ export class Pagination extends Component {
|
|
|
393
376
|
this.state.page.current = nextPage;
|
|
394
377
|
if (shouldLock) this.state.locked = true;
|
|
395
378
|
});
|
|
396
|
-
this.props.page.current = nextPage;
|
|
397
379
|
|
|
398
380
|
if (typeof this.props.onChange === 'function') {
|
|
399
381
|
let result: void | Promise<unknown>;
|
|
400
382
|
try {
|
|
401
383
|
result = this.props.onChange(nextPage, this);
|
|
402
384
|
} catch (error) {
|
|
403
|
-
|
|
404
|
-
flushSync(() => {
|
|
405
|
-
this.state.locked = false;
|
|
406
|
-
});
|
|
407
|
-
}
|
|
385
|
+
this.unlock(changeId);
|
|
408
386
|
throw error;
|
|
409
387
|
}
|
|
410
388
|
|
|
411
389
|
if (shouldLock && result && typeof result.then === 'function') {
|
|
412
|
-
const unlock = () =>
|
|
413
|
-
if (!this.runtime.destroyed && changeId === this.runtime.changeId) {
|
|
414
|
-
flushSync(() => {
|
|
415
|
-
this.state.locked = false;
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
};
|
|
390
|
+
const unlock = () => this.unlock(changeId);
|
|
419
391
|
void Promise.resolve(result).then(unlock, unlock);
|
|
420
|
-
} else
|
|
421
|
-
|
|
422
|
-
this.state.locked = false;
|
|
423
|
-
});
|
|
392
|
+
} else {
|
|
393
|
+
this.unlock(changeId);
|
|
424
394
|
}
|
|
425
395
|
}
|
|
426
396
|
|
|
427
397
|
return this;
|
|
428
398
|
}
|
|
429
399
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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');
|
|
437
421
|
validateParam(
|
|
438
|
-
'
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
'Pagination.
|
|
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'
|
|
442
432
|
);
|
|
443
433
|
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
...newProps.page,
|
|
457
|
-
},
|
|
458
|
-
count: {
|
|
459
|
-
...this.props.count,
|
|
460
|
-
...newProps.count,
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
const pageCount = this._getPageCount(props.total, props.page.size);
|
|
464
|
-
props.page.current = clamp(props.page.current, 1, pageCount);
|
|
465
|
-
|
|
466
|
-
this.props = props;
|
|
467
|
-
flushSync(() => {
|
|
468
|
-
this.state.total = props.total;
|
|
469
|
-
this.state.page.size = props.page.size;
|
|
470
|
-
this.state.page.current = props.page.current;
|
|
471
|
-
this.state.count.sibling = props.count.sibling;
|
|
472
|
-
this.state.count.boundary = props.count.boundary;
|
|
473
|
-
this.state.pageCount = pageCount;
|
|
474
|
-
if (!props.lock) this.state.locked = false;
|
|
475
|
-
});
|
|
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
|
+
}
|
|
476
446
|
|
|
477
|
-
|
|
447
|
+
this.renderItems();
|
|
478
448
|
}
|
|
479
449
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
450
|
+
private renderItems(): void {
|
|
451
|
+
if (!this.dom.list) return;
|
|
483
452
|
|
|
484
|
-
|
|
485
|
-
|
|
453
|
+
const items = this.getPageItems();
|
|
454
|
+
const itemsKey = [
|
|
486
455
|
this.state.page.current,
|
|
487
456
|
this.state.pageCount,
|
|
488
|
-
this.
|
|
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')
|
|
489
469
|
);
|
|
490
470
|
}
|
|
491
471
|
|
|
492
|
-
|
|
493
|
-
return
|
|
494
|
-
this.state.
|
|
495
|
-
this.
|
|
496
|
-
|
|
497
|
-
].join('|');
|
|
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
|
+
});
|
|
498
477
|
}
|
|
499
478
|
|
|
500
|
-
|
|
479
|
+
private isLocked(): boolean {
|
|
501
480
|
return this.props.lock && this.state.locked;
|
|
502
481
|
}
|
|
503
482
|
|
|
504
|
-
|
|
505
|
-
return this.
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
_isNextDisabled(): boolean {
|
|
509
|
-
return this._isLocked() || this.state.page.current >= this.state.pageCount;
|
|
483
|
+
private isPrevDisabled(): boolean {
|
|
484
|
+
return this.isLocked() || this.state.page.current <= 1;
|
|
510
485
|
}
|
|
511
486
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return [button, buttonIcon, buttonGhost, ...extra]
|
|
515
|
-
.filter(Boolean)
|
|
516
|
-
.join(' ');
|
|
487
|
+
private isNextDisabled(): boolean {
|
|
488
|
+
return this.isLocked() || this.state.page.current >= this.state.pageCount;
|
|
517
489
|
}
|
|
518
490
|
|
|
519
|
-
|
|
520
|
-
const
|
|
491
|
+
private buildControlItem(type: PageAction): HTMLElement {
|
|
492
|
+
const disabled =
|
|
493
|
+
type === 'prev' ? this.isPrevDisabled() : this.isNextDisabled();
|
|
494
|
+
return jsx('li', {
|
|
521
495
|
className: this.props.className.item,
|
|
522
496
|
'data-pagination-control': type,
|
|
523
497
|
children: jsx('button', {
|
|
524
|
-
className: this.
|
|
498
|
+
className: this.props.className.button,
|
|
525
499
|
type: 'button',
|
|
526
500
|
'data-page-action': type,
|
|
501
|
+
'aria-label':
|
|
502
|
+
type === 'prev' ? 'Go to previous page' : 'Go to next page',
|
|
503
|
+
'aria-disabled': disabled ? 'true' : 'false',
|
|
504
|
+
tabindex: disabled ? '-1' : null,
|
|
505
|
+
disabled,
|
|
527
506
|
children: icon(type === 'prev' ? 'arrow-left' : 'arrow-right'),
|
|
528
507
|
}),
|
|
529
508
|
}) as HTMLElement;
|
|
530
|
-
|
|
531
|
-
return item;
|
|
532
509
|
}
|
|
533
510
|
|
|
534
|
-
|
|
535
|
-
const disabled = this.
|
|
511
|
+
private buildPageItem(item: PaginationItem): HTMLElement {
|
|
512
|
+
const disabled = this.isLocked();
|
|
536
513
|
|
|
537
514
|
if (item.type === 'more') {
|
|
538
515
|
return jsx('li', {
|
|
@@ -540,114 +517,53 @@ export class Pagination extends Component {
|
|
|
540
517
|
.filter(Boolean)
|
|
541
518
|
.join(' '),
|
|
542
519
|
'data-pagination-more': item.key,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
className: this._buttonClass(),
|
|
546
|
-
type: 'button',
|
|
547
|
-
disabled,
|
|
520
|
+
children: jsx('span', {
|
|
521
|
+
className: this.props.className.button,
|
|
548
522
|
children: icon('more'),
|
|
549
523
|
}),
|
|
550
524
|
}) as HTMLElement;
|
|
551
525
|
}
|
|
552
526
|
|
|
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
|
+
}
|
|
545
|
+
|
|
553
546
|
return jsx('li', {
|
|
554
547
|
className: this.props.className.item,
|
|
555
548
|
'data-pagination-item': String(item.page),
|
|
556
|
-
children:
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
'data-current-page': String(item.page),
|
|
567
|
-
'aria-current': 'page',
|
|
568
|
-
'aria-label': `Page ${item.page}, current page`,
|
|
569
|
-
children: disabled
|
|
570
|
-
? jsx('i', {
|
|
571
|
-
className: this.props.className.loading,
|
|
572
|
-
children: icon('loader'),
|
|
573
|
-
})
|
|
574
|
-
: String(item.page),
|
|
575
|
-
})
|
|
576
|
-
: jsx('button', {
|
|
577
|
-
className: this._buttonClass(),
|
|
578
|
-
type: 'button',
|
|
579
|
-
'data-page': String(item.page),
|
|
580
|
-
'aria-label': `Go to page ${item.page}`,
|
|
581
|
-
'aria-disabled': disabled ? 'true' : null,
|
|
582
|
-
disabled,
|
|
583
|
-
tabindex: disabled ? '-1' : null,
|
|
584
|
-
children: String(item.page),
|
|
585
|
-
}),
|
|
549
|
+
children: jsx('button', {
|
|
550
|
+
className: this.props.className.button,
|
|
551
|
+
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),
|
|
558
|
+
}),
|
|
586
559
|
}) as HTMLElement;
|
|
587
560
|
}
|
|
588
561
|
|
|
589
|
-
|
|
590
|
-
this.
|
|
591
|
-
this.
|
|
592
|
-
const prev = this.dom.prev
|
|
593
|
-
? q<HTMLButtonElement>('[data-page-action]', this.dom.prev)
|
|
594
|
-
: null;
|
|
595
|
-
const next = this.dom.next
|
|
596
|
-
? q<HTMLButtonElement>('[data-page-action]', this.dom.next)
|
|
597
|
-
: null;
|
|
598
|
-
|
|
599
|
-
if (prev) {
|
|
600
|
-
bindAttr(prev, 'disabled', () => this._isPrevDisabled());
|
|
601
|
-
bindAttr(prev, 'aria-disabled', () =>
|
|
602
|
-
this._isPrevDisabled() ? 'true' : 'false'
|
|
603
|
-
);
|
|
604
|
-
bindAttr(prev, 'tabindex', () =>
|
|
605
|
-
this._isPrevDisabled() ? '-1' : null
|
|
606
|
-
);
|
|
607
|
-
bindAttr(prev, 'aria-label', () => 'Go to previous page');
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (next) {
|
|
611
|
-
bindAttr(next, 'disabled', () => this._isNextDisabled());
|
|
612
|
-
bindAttr(next, 'aria-disabled', () =>
|
|
613
|
-
this._isNextDisabled() ? 'true' : 'false'
|
|
614
|
-
);
|
|
615
|
-
bindAttr(next, 'tabindex', () =>
|
|
616
|
-
this._isNextDisabled() ? '-1' : null
|
|
617
|
-
);
|
|
618
|
-
bindAttr(next, 'aria-label', () => 'Go to next page');
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return dispose;
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
_bindPageItems(): void {
|
|
626
|
-
this.cleanup.itemsEffect?.dispose();
|
|
627
|
-
this.cleanup.itemsEffect = createEffect(() => {
|
|
628
|
-
const nextKey = this._getItemsKey();
|
|
629
|
-
if (nextKey === this.runtime.itemsKey) return;
|
|
630
|
-
|
|
631
|
-
this.runtime.itemsKey = nextKey;
|
|
632
|
-
this._renderPageItems();
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
_renderPageItems(): void {
|
|
637
|
-
this.dom.items = [];
|
|
638
|
-
if (!this.dom.list || !this.dom.next) return;
|
|
639
|
-
|
|
640
|
-
const items = this._getPageItems();
|
|
641
|
-
const nodes = items.map((item) => this._buildPageItem(item));
|
|
642
|
-
|
|
643
|
-
for (const node of this.dom.pageNodes) node.remove();
|
|
644
|
-
this.dom.pageNodes = nodes;
|
|
645
|
-
for (const node of nodes) this.dom.list.insertBefore(node, this.dom.next);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
_bindEvents(): void {
|
|
649
|
-
const root = this.root;
|
|
562
|
+
private bindEvents(): void {
|
|
563
|
+
this.unbindEvents();
|
|
564
|
+
const root = this.dom.root;
|
|
650
565
|
if (!root) return;
|
|
566
|
+
|
|
651
567
|
this.cleanup.events.on('click', root, 'click', (event) => {
|
|
652
568
|
const source = event.target;
|
|
653
569
|
if (!(source instanceof Element)) return;
|
|
@@ -660,12 +576,12 @@ export class Pagination extends Component {
|
|
|
660
576
|
event.preventDefault();
|
|
661
577
|
|
|
662
578
|
const action = target.dataset.pageAction;
|
|
663
|
-
if (action === 'prev' && !this.
|
|
579
|
+
if (action === 'prev' && !this.isPrevDisabled()) {
|
|
664
580
|
this.go(this.state.page.current - 1);
|
|
665
581
|
return;
|
|
666
582
|
}
|
|
667
583
|
|
|
668
|
-
if (action === 'next' && !this.
|
|
584
|
+
if (action === 'next' && !this.isNextDisabled()) {
|
|
669
585
|
this.go(this.state.page.current + 1);
|
|
670
586
|
return;
|
|
671
587
|
}
|
|
@@ -676,36 +592,90 @@ export class Pagination extends Component {
|
|
|
676
592
|
});
|
|
677
593
|
}
|
|
678
594
|
|
|
679
|
-
|
|
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 {
|
|
680
612
|
if (this.runtime.destroyed) {
|
|
681
613
|
throw new Error(`Pagination.${method}: instance has been destroyed.`);
|
|
682
614
|
}
|
|
615
|
+
if (!this.runtime.built) {
|
|
616
|
+
throw new Error(`Pagination.${method}: call build() first.`);
|
|
617
|
+
}
|
|
683
618
|
}
|
|
684
619
|
|
|
685
|
-
protected
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
+
}
|
|
691
638
|
|
|
692
|
-
|
|
639
|
+
protected override validateStatePatch(patch: Partial<PaginationState>): void {
|
|
640
|
+
validateParam(
|
|
641
|
+
'state',
|
|
642
|
+
patch,
|
|
643
|
+
{
|
|
644
|
+
type: 'plainObject',
|
|
645
|
+
},
|
|
646
|
+
'Pagination.setState'
|
|
647
|
+
);
|
|
693
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.`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
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();
|
|
694
670
|
this.runtime.built = false;
|
|
695
671
|
this.runtime.itemsKey = '';
|
|
696
672
|
this.runtime.changeId = 0;
|
|
697
|
-
this.dom.container = null;
|
|
698
673
|
this.dom.list = null;
|
|
699
|
-
this.dom.prev = null;
|
|
700
|
-
this.dom.next = null;
|
|
701
|
-
this.dom.pageNodes = [];
|
|
702
|
-
this.dom.items = [];
|
|
703
674
|
}
|
|
704
675
|
}
|
|
705
676
|
|
|
706
|
-
export
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return new Pagination(container, props);
|
|
677
|
+
export type Pagination = PaginationComponent;
|
|
678
|
+
|
|
679
|
+
export function createPagination(input: PaginationProps = {}): Pagination {
|
|
680
|
+
return new PaginationComponent(input);
|
|
711
681
|
}
|