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.
Files changed (103) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +1273 -1896
  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 +22 -3
  7. package/src/components/accordion.ts +297 -216
  8. package/src/components/flow.ts +222 -258
  9. package/src/components/form.ts +44 -102
  10. package/src/components/menu.ts +268 -356
  11. package/src/components/modal.ts +181 -641
  12. package/src/components/offcanvas.ts +225 -106
  13. package/src/components/pagination.ts +296 -326
  14. package/src/components/sticky.ts +55 -54
  15. package/src/components/swiper.ts +237 -247
  16. package/src/components/tabs.ts +278 -271
  17. package/src/components/toc.ts +42 -46
  18. package/src/core/Component.ts +69 -47
  19. package/src/css/accordion.css +2 -2
  20. package/src/css/animation.css +1 -5
  21. package/src/css/avatar.css +1 -1
  22. package/src/css/badge.css +1 -1
  23. package/src/css/breadcrumb.css +9 -10
  24. package/src/css/button.css +1 -1
  25. package/src/css/card.css +2 -2
  26. package/src/css/color.css +974 -1002
  27. package/src/css/color_p3.css +974 -1004
  28. package/src/css/components.css +1 -1
  29. package/src/css/content.css +72 -63
  30. package/src/css/drop.css +1 -1
  31. package/src/css/editor.css +14 -14
  32. package/src/css/flow.css +4 -14
  33. package/src/css/font.css +5 -4
  34. package/src/css/form.css +76 -52
  35. package/src/css/gradient.css +299 -222
  36. package/src/css/icon.css +57 -57
  37. package/src/css/index.css +1 -1
  38. package/src/css/layout.css +13 -10
  39. package/src/css/loading.css +60 -12
  40. package/src/css/menu.css +262 -188
  41. package/src/css/modal.css +1 -1
  42. package/src/css/offcanvas.css +9 -13
  43. package/src/css/pagination.css +5 -1
  44. package/src/css/popup.css +1 -1
  45. package/src/css/skeleton.css +3 -2
  46. package/src/css/status.css +2 -3
  47. package/src/css/swiper.css +1 -1
  48. package/src/css/table.css +1 -1
  49. package/src/css/tabs.css +12 -11
  50. package/src/css/tag.css +1 -1
  51. package/src/css/theme-palette.css +19 -5
  52. package/src/css/themes/default/accordion.css +61 -61
  53. package/src/css/themes/default/avatar.css +54 -54
  54. package/src/css/themes/default/badge.css +62 -62
  55. package/src/css/themes/default/breadcrumb.css +23 -23
  56. package/src/css/themes/default/button.css +203 -203
  57. package/src/css/themes/default/content.css +53 -53
  58. package/src/css/themes/default/drop.css +10 -10
  59. package/src/css/themes/default/editor.css +9 -9
  60. package/src/css/themes/default/index.css +1 -1
  61. package/src/css/themes/default/loading.css +26 -20
  62. package/src/css/themes/default/menu.css +75 -11
  63. package/src/css/themes/default/modal.css +32 -36
  64. package/src/css/themes/default/pagination.css +7 -7
  65. package/src/css/themes/default/popup.css +6 -6
  66. package/src/css/themes/default/swiper.css +32 -32
  67. package/src/css/themes/default/table.css +16 -16
  68. package/src/css/themes/default/tabs.css +30 -30
  69. package/src/css/themes/default/tag.css +71 -71
  70. package/src/css/themes/default/theme-palette.css +11 -11
  71. package/src/css/themes/default/tip.css +42 -42
  72. package/src/css/themes/default/toast.css +60 -60
  73. package/src/css/themes/default/toolbar.css +23 -29
  74. package/src/css/themes/default/tooltip.css +43 -13
  75. package/src/css/themes/index.css +1 -1
  76. package/src/css/tip.css +2 -2
  77. package/src/css/toast.css +3 -3
  78. package/src/css/tokens.css +12 -17
  79. package/src/css/toolbar.css +9 -10
  80. package/src/css/tooltip.css +3 -2
  81. package/src/index.ts +22 -19
  82. package/src/{components → primitives}/drop.ts +58 -49
  83. package/src/{components → primitives}/icons.ts +1 -1
  84. package/src/primitives/loading.ts +23 -0
  85. package/src/primitives/parabola.ts +388 -0
  86. package/src/primitives/popup.ts +61 -0
  87. package/src/{components → primitives}/theme.ts +88 -57
  88. package/src/{components → primitives}/toast.ts +18 -25
  89. package/src/{components → primitives}/tooltip.ts +100 -25
  90. package/src/utilities/browser.ts +23 -49
  91. package/src/utilities/class-name.ts +5 -0
  92. package/src/utilities/dom.ts +14 -32
  93. package/src/utilities/function.ts +4 -0
  94. package/src/utilities/http.ts +7 -7
  95. package/src/utilities/id.ts +57 -0
  96. package/src/utilities/object.ts +5 -0
  97. package/src/utilities/timer.ts +38 -0
  98. package/src/utilities/types.ts +420 -0
  99. package/src/{components → validation}/validator.ts +64 -53
  100. package/src/components/parabola.ts +0 -335
  101. package/src/utilities/cache.ts +0 -57
  102. package/src/utilities/core.ts +0 -614
  103. package/src/utilities/storage.ts +0 -46
@@ -2,32 +2,31 @@ import {
2
2
  bindAttr,
3
3
  bindClass,
4
4
  createDeepStore,
5
+ createEffect,
5
6
  createRoot,
6
7
  h,
8
+ untrack,
7
9
  } from 'vanilla-signal';
8
10
 
9
11
  import Component, {
10
12
  type ComponentDOM,
11
13
  type ComponentRuntime,
12
- type ComponentUpdateOptions,
13
14
  } from '../core/Component.ts';
14
- import {
15
- type ResolveSchema,
16
- isPlainObject,
17
- resolveProps,
18
- validateParam,
19
- } from '../utilities/core.ts';
15
+ import { icon } from '../primitives/icons.ts';
16
+ import { joinClasses } from '../utilities/class-name.ts';
20
17
  import {
21
18
  all,
22
- type DOMReference,
23
19
  isElement,
24
- isRenderableContent,
25
20
  normalizeContentNodes,
26
21
  q,
27
22
  type RenderableContent,
28
- requireContainer,
29
23
  } from '../utilities/dom.ts';
30
- import { icon } from './icons.ts';
24
+ import { randomId } from '../utilities/id.ts';
25
+ import {
26
+ type ResolveSchema,
27
+ resolveProps,
28
+ validateParam,
29
+ } from '../utilities/types.ts';
31
30
 
32
31
  const SWIPE_THRESHOLD = 6;
33
32
  const AUTOPLAY_DELAY_FLOOR = 16;
@@ -76,8 +75,9 @@ export interface SwiperSlideContext {
76
75
  index: number;
77
76
  }
78
77
 
79
- export interface SwiperOptions extends Record<string, unknown> {
80
- data?: SwiperDataItem[] | null;
78
+ export interface SwiperProps extends Record<string, unknown> {
79
+ id?: string | null;
80
+ data?: SwiperDataItem[];
81
81
  loop?: boolean;
82
82
  autoplay?: boolean;
83
83
  delay?: number;
@@ -93,8 +93,9 @@ export interface SwiperOptions extends Record<string, unknown> {
93
93
  className?: SwiperClassNameConfig;
94
94
  }
95
95
 
96
- interface ResolvedSwiperOptions extends Record<string, unknown> {
97
- data: SwiperDataItem[] | null;
96
+ interface ResolvedSwiperProps extends Record<string, unknown> {
97
+ id: string | null;
98
+ data: SwiperDataItem[];
98
99
  loop: boolean;
99
100
  autoplay: boolean;
100
101
  delay: number;
@@ -111,6 +112,7 @@ interface ResolvedSwiperOptions extends Record<string, unknown> {
111
112
  }
112
113
 
113
114
  interface SwiperState extends Record<string, unknown> {
115
+ data: SwiperDataItem[];
114
116
  index: number;
115
117
  trackIndex: number;
116
118
  transform: number;
@@ -120,10 +122,7 @@ interface SwiperState extends Record<string, unknown> {
120
122
 
121
123
  interface SwiperDOM extends ComponentDOM {
122
124
  root: HTMLElement | null;
123
- container: DOMReference;
124
- mountTarget: Element | null;
125
125
  createdRoot: boolean;
126
- createdSlides: boolean;
127
126
  wrapper: HTMLElement | null;
128
127
  slides: HTMLElement[];
129
128
  pagination: HTMLElement | null;
@@ -149,6 +148,7 @@ interface SwipePoint {
149
148
  }
150
149
 
151
150
  interface SwiperRuntime extends ComponentRuntime {
151
+ built: boolean;
152
152
  logs: SwipeLog[];
153
153
  startTarget: EventTarget | null;
154
154
  touching: boolean;
@@ -163,9 +163,9 @@ interface SwiperRuntime extends ComponentRuntime {
163
163
  interface SwiperCleanupExtras {
164
164
  bindings?: (() => void) | null;
165
165
  navBindings?: (() => void) | null;
166
+ data?: (() => void) | null;
166
167
  }
167
168
 
168
- type SwiperUpdateOptions = Partial<ComponentUpdateOptions>;
169
169
  type SwiperDirection = 'prev' | 'next';
170
170
  type CreatedNavigationKey = 'createdPrevButton' | 'createdNextButton';
171
171
 
@@ -192,18 +192,20 @@ const DEFAULT_CLASS_NAMES: SwiperClassNames = {
192
192
  };
193
193
 
194
194
  const SWIPER_OPTIONS_SCHEMA = {
195
- data: {
195
+ id: {
196
196
  default: null,
197
- validate: (value: unknown) => value == null || Array.isArray(value),
198
- message: 'expects an array or null.',
197
+ types: ['string', 'null'],
198
+ nonEmpty: true,
199
+ normalize: (value: unknown) =>
200
+ typeof value === 'string' ? value.trim() : value,
199
201
  },
202
+ data: { default: [], type: 'array' },
200
203
  loop: { default: true, type: 'boolean' },
201
204
  autoplay: { default: true, type: 'boolean' },
202
205
  delay: {
203
206
  default: 3000,
204
207
  type: 'number',
205
- validate: (value: unknown) => Number(value) >= 0,
206
- message: 'expects a positive number or 0.',
208
+ min: 0,
207
209
  },
208
210
  lazyload: { default: true, type: 'boolean' },
209
211
  pagination: { default: true, type: 'boolean' },
@@ -211,32 +213,29 @@ const SWIPER_OPTIONS_SCHEMA = {
211
213
  speed: {
212
214
  default: 300,
213
215
  type: 'number',
214
- validate: (value: unknown) => Number(value) >= 0,
215
- message: 'expects a positive number or 0.',
216
+ min: 0,
216
217
  },
217
218
  touchRatio: {
218
219
  default: 1,
219
220
  type: 'number',
220
- validate: (value: unknown) => Number(value) > 0,
221
- message: 'expects a number greater than 0.',
221
+ greaterThan: 0,
222
222
  },
223
223
  touchAngle: {
224
224
  default: 45,
225
225
  type: 'number',
226
- validate: (value: unknown) => Number(value) >= 0 && Number(value) <= 90,
227
- message: 'expects a number between 0 and 90.',
226
+ min: 0,
227
+ max: 90,
228
228
  },
229
229
  longSwipesMs: {
230
230
  default: 300,
231
231
  type: 'number',
232
- validate: (value: unknown) => Number(value) >= 0,
233
- message: 'expects a positive number or 0.',
232
+ min: 0,
234
233
  },
235
234
  longSwipesRatio: {
236
235
  default: 0.05,
237
236
  type: 'number',
238
- validate: (value: unknown) => Number(value) >= 0 && Number(value) <= 1,
239
- message: 'expects a number between 0 and 1.',
237
+ min: 0,
238
+ max: 1,
240
239
  },
241
240
  preventClick: { default: true, type: 'boolean' },
242
241
  className: {
@@ -247,26 +246,27 @@ const SWIPER_OPTIONS_SCHEMA = {
247
246
  ...(value && typeof value === 'object' ? value : {}),
248
247
  }),
249
248
  },
250
- } satisfies ResolveSchema<SwiperOptions>;
249
+ } satisfies ResolveSchema<SwiperProps>;
251
250
 
252
251
  const SWIPER_DATA_ITEM_RULE = {
253
- validate: (value: unknown) => {
254
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
255
- return false;
256
- }
257
- const item = value as SwiperDataItem;
258
-
259
- return (
260
- (item.image == null || typeof item.image === 'string') &&
261
- (item.url == null || typeof item.url === 'string') &&
262
- (item.title == null || typeof item.title === 'string') &&
263
- (item.sort == null || typeof item.sort === 'number') &&
264
- (item.blank == null || typeof item.blank === 'boolean') &&
265
- (item.children == null || isRenderableContent(item.children))
266
- );
252
+ type: 'plainObject',
253
+ shape: {
254
+ image: ['string', 'null', 'undefined'],
255
+ url: ['string', 'null', 'undefined'],
256
+ title: ['string', 'null', 'undefined'],
257
+ sort: ['number', 'null', 'undefined'],
258
+ blank: ['boolean', 'null', 'undefined'],
259
+ children: 'renderable',
267
260
  },
268
- message:
269
- 'expects items with optional image, url, title, sort, blank and children fields.',
261
+ };
262
+
263
+ const SWIPER_STATE_SCHEMA = {
264
+ data: SWIPER_OPTIONS_SCHEMA.data,
265
+ index: { type: 'number' },
266
+ trackIndex: { type: 'number' },
267
+ transform: { type: 'number' },
268
+ animating: { type: 'boolean' },
269
+ width: { type: 'number' },
270
270
  };
271
271
 
272
272
  function isInteractiveTarget(target: EventTarget | null): boolean {
@@ -290,42 +290,39 @@ function getRenderableNodes(
290
290
  return normalizeContentNodes(content, context);
291
291
  }
292
292
 
293
- function normalizeOptions(options: SwiperOptions = {}): ResolvedSwiperOptions {
293
+ function cloneData(data: SwiperDataItem[] | undefined): SwiperDataItem[] {
294
+ if (!Array.isArray(data)) return [];
295
+ return data.map((item) => ({ ...item }));
296
+ }
297
+
298
+ function normalizeProps(input: SwiperProps = {}): ResolvedSwiperProps {
294
299
  const props = resolveProps(
295
- options,
300
+ input,
296
301
  SWIPER_OPTIONS_SCHEMA,
297
- 'Swiper.options'
298
- ) as ResolvedSwiperOptions;
302
+ 'Swiper.props'
303
+ ) as ResolvedSwiperProps;
299
304
  return {
300
305
  ...props,
306
+ id: props.id?.trim() || null,
307
+ data: cloneData(props.data),
301
308
  className: { ...props.className },
302
309
  };
303
310
  }
304
311
 
305
- function resolveSwiperRoot(container: Element): HTMLElement {
306
- const roots = all<HTMLElement>('[data-swiper="root"]', container);
312
+ function findUniqueElementById(id: string): HTMLElement | null {
313
+ const matches = Array.from(
314
+ document.querySelectorAll<HTMLElement>('[id]')
315
+ ).filter((element) => element.id === id);
307
316
  validateParam(
308
- 'container',
309
- roots,
317
+ 'id',
318
+ matches,
310
319
  {
311
- validate: (value: unknown) => Array.isArray(value) && value.length === 1,
312
- message: 'expects exactly one [data-swiper="root"] descendant.',
320
+ type: 'array',
321
+ maxLength: 1,
313
322
  },
314
- 'Swiper'
323
+ 'Swiper.props'
315
324
  );
316
- return roots[0] as HTMLElement;
317
- }
318
-
319
- function classList(...tokens: Array<string | null | undefined>): string {
320
- return tokens.filter(Boolean).join(' ');
321
- }
322
-
323
- function setDataMarker(element: Element, name: string, value = ''): void {
324
- element.setAttribute(name, value);
325
- }
326
-
327
- function isObjectPatch(value: unknown): value is SwiperOptions {
328
- return isPlainObject(value);
325
+ return matches[0] || null;
329
326
  }
330
327
 
331
328
  /**
@@ -334,35 +331,27 @@ function isObjectPatch(value: unknown): value is SwiperOptions {
334
331
  * 支持链接 slide、图片 lazyload、分页、导航、loop 和桌面/移动端拖拽滑动。
335
332
  * 使用 vanilla-signal 响应式管理 pagination 和 navigation 状态。
336
333
  */
337
- export class Swiper extends Component<
338
- ResolvedSwiperOptions,
334
+ class SwiperComponent extends Component<
335
+ ResolvedSwiperProps,
339
336
  SwiperState,
340
337
  SwiperDOM
341
338
  > {
342
- declare props: ResolvedSwiperOptions;
339
+ declare props: ResolvedSwiperProps;
343
340
  declare state: SwiperState;
344
341
  declare dom: SwiperDOM;
345
342
  declare runtime: SwiperRuntime;
346
343
  declare cleanup: Component['cleanup'] & SwiperCleanupExtras;
347
344
 
348
- private _built: boolean;
349
-
350
345
  /**
351
346
  * 创建轮播实例。
352
- * @param {Element|Node|string|Array} container 挂载容器、选择器或 JSX/h 返回节点。
353
- * @param {object} [options={}] Swiper 配置。
347
+ * @param {object} [props={}] Swiper 配置。
354
348
  */
355
- constructor(container: DOMReference, options: SwiperOptions = {}) {
356
- const resolvedOptions = normalizeOptions(options);
349
+ constructor(props: SwiperProps = {}) {
350
+ const resolvedOptions = normalizeProps(props);
357
351
 
358
352
  super(resolvedOptions);
359
353
 
360
- this.dom.container = container;
361
- this._built = false;
362
-
363
- this.dom.mountTarget = null;
364
354
  this.dom.createdRoot = false;
365
- this.dom.createdSlides = false;
366
355
  this.dom.wrapper = null;
367
356
  this.dom.slides = [];
368
357
  this.dom.pagination = null;
@@ -373,6 +362,11 @@ export class Swiper extends Component<
373
362
  this.dom.createdPrevButton = false;
374
363
  this.dom.createdNextButton = false;
375
364
 
365
+ this.cleanup.data = null;
366
+ this.cleanup.bindings = null;
367
+ this.cleanup.navBindings = null;
368
+
369
+ this.runtime.built = false;
376
370
  this.runtime.logs = [];
377
371
  this.runtime.startTarget = null;
378
372
  this.runtime.touching = false;
@@ -385,6 +379,7 @@ export class Swiper extends Component<
385
379
  this.runtime.realCount = 0;
386
380
 
387
381
  this.state = createDeepStore({
382
+ data: cloneData(this.props.data),
388
383
  index: 0,
389
384
  trackIndex: 0,
390
385
  transform: 0,
@@ -394,39 +389,31 @@ export class Swiper extends Component<
394
389
  }
395
390
 
396
391
  /**
397
- * 构建或绑定 Swiper DOM。
392
+ * 构建 Swiper DOM。
398
393
  * @returns {Swiper} 当前实例。
399
394
  */
400
395
  build(): this {
401
396
  if (this.runtime.destroyed)
402
397
  throw new Error('Swiper.build: instance destroyed');
403
- if (this._built) return this;
404
-
405
- const hasData = Array.isArray(this.props.data);
406
- const root = requireContainer(this.dom.container, 'Swiper');
407
-
408
- let mountRoot = null;
409
- let mountTarget = null;
410
-
411
- if (hasData) {
412
- mountTarget = root;
413
- mountRoot = h('div', {
398
+ if (this.runtime.built) return this;
399
+
400
+ const boundRoot = this.props.id
401
+ ? findUniqueElementById(this.props.id)
402
+ : null;
403
+ const mountRoot =
404
+ boundRoot ||
405
+ (h('div', {
406
+ id: this.props.id || randomId(),
414
407
  className: this.props.className.root,
415
408
  'data-swiper': 'root',
416
- }) as HTMLElement;
417
- mountTarget.appendChild(mountRoot);
418
- } else {
419
- mountRoot = resolveSwiperRoot(root);
420
- }
409
+ }) as HTMLElement);
421
410
 
422
411
  try {
423
412
  this.dom.root = mountRoot;
424
- this.dom.mountTarget = mountTarget;
425
- this.dom.createdRoot = hasData;
426
- this.dom.createdSlides = hasData;
413
+ this.dom.createdRoot = !boundRoot;
427
414
 
428
- const wrapper = hasData
429
- ? this.createDataView(mountRoot, this.props.data)
415
+ const wrapper = this.dom.createdRoot
416
+ ? this.createDataView(mountRoot, this.state.data)
430
417
  : q<HTMLElement>('[data-swiper-wrapper]', mountRoot);
431
418
  validateParam(
432
419
  'wrapper',
@@ -461,9 +448,10 @@ export class Swiper extends Component<
461
448
  this.dom.createdNextButton = false;
462
449
 
463
450
  this.runtime.realCount = this.dom.slides.length;
464
- this._built = true;
451
+ this.runtime.built = true;
465
452
 
466
453
  if (this.dom.slides.length > 0) this.onInit();
454
+ if (this.dom.createdRoot) this.bindStateData();
467
455
  } catch (error) {
468
456
  this.destroy();
469
457
  throw error;
@@ -502,15 +490,15 @@ export class Swiper extends Component<
502
490
  return this.toRealIndex();
503
491
  }
504
492
 
505
- assertBuilt(method: string): void {
506
- if (!this._built) {
493
+ private assertBuilt(method: string): void {
494
+ if (!this.runtime.built) {
507
495
  throw new Error(`Swiper.${method}: call build() first.`);
508
496
  }
509
497
  }
510
498
 
511
- createDataView(
499
+ private createDataView(
512
500
  root: HTMLElement,
513
- data: SwiperDataItem[] | null
501
+ data: SwiperDataItem[]
514
502
  ): HTMLElement {
515
503
  const items = this.normalizeData(data);
516
504
  const wrapper = h('div', {
@@ -528,9 +516,57 @@ export class Swiper extends Component<
528
516
  return wrapper;
529
517
  }
530
518
 
531
- normalizeData(data: SwiperDataItem[] | null): NormalizedSwiperDataItem[] {
532
- if (!Array.isArray(data)) return [];
519
+ private bindStateData(): void {
520
+ if (this.cleanup.data) return;
521
+
522
+ this.cleanup.data = createRoot((dispose) => {
523
+ let initialized = false;
524
+ createEffect(() => {
525
+ const data = this.state.data;
526
+ if (!initialized) {
527
+ initialized = true;
528
+ return;
529
+ }
530
+ untrack(() => this.syncStateData(data));
531
+ });
532
+ return dispose;
533
+ });
534
+ }
535
+
536
+ private syncStateData(data: SwiperDataItem[]): void {
537
+ if (!this.runtime.built || !this.dom.createdRoot) return;
538
+ validateParam('data', data, SWIPER_OPTIONS_SCHEMA.data, 'Swiper.state');
533
539
 
540
+ const realIndex = this.state.index;
541
+ let wrapper = this.dom.wrapper;
542
+ if (!wrapper && this.dom.root) {
543
+ wrapper = this.createDataView(this.dom.root, data);
544
+ this.dom.wrapper = wrapper;
545
+ }
546
+ if (!wrapper) return;
547
+
548
+ this.pause();
549
+ this.clearImageCleanups();
550
+ this.cleanup.bindings?.();
551
+ this.cleanup.bindings = null;
552
+ this.cleanup.navBindings?.();
553
+ this.cleanup.navBindings = null;
554
+ this.cleanup.events.clear();
555
+
556
+ all('[data-clone]', wrapper).forEach((slide) => slide.remove());
557
+ wrapper.textContent = '';
558
+ this.normalizeData(data).forEach((item, index) => {
559
+ wrapper.appendChild(this.createDataSlide(item, index));
560
+ });
561
+ this.refreshSlides();
562
+ this.runtime.realCount = this.dom.slides.length;
563
+ this.setState({
564
+ index: Math.min(realIndex, Math.max(0, this.realCount - 1)),
565
+ });
566
+ this.reInitView();
567
+ }
568
+
569
+ private normalizeData(data: SwiperDataItem[]): NormalizedSwiperDataItem[] {
534
570
  data.forEach((item, index) => {
535
571
  validateParam(
536
572
  String(index),
@@ -555,7 +591,10 @@ export class Swiper extends Component<
555
591
  });
556
592
  }
557
593
 
558
- createDataSlide(item: NormalizedSwiperDataItem, index: number): HTMLElement {
594
+ private createDataSlide(
595
+ item: NormalizedSwiperDataItem,
596
+ index: number
597
+ ): HTMLElement {
559
598
  const slide = h(item.url ? 'a' : 'div', {
560
599
  className: this.props.className.slide,
561
600
  href: item.url || undefined,
@@ -621,6 +660,8 @@ export class Swiper extends Component<
621
660
  this.pause();
622
661
  this.clearImageCleanups();
623
662
  this.cleanup.events.clear();
663
+ this.cleanup.data?.();
664
+ this.cleanup.data = null;
624
665
  this.cleanup.bindings?.();
625
666
  this.cleanup.bindings = null;
626
667
  this.cleanup.navBindings?.();
@@ -633,16 +674,27 @@ export class Swiper extends Component<
633
674
  if (this.dom.createdPrevButton) this.dom.prevButton?.remove();
634
675
  if (this.dom.createdNextButton) this.dom.nextButton?.remove();
635
676
  if (this.dom.createdRoot) this.dom.root?.remove();
636
- this._built = false;
677
+ this.runtime.built = false;
637
678
  }
638
679
 
639
- updateSize(): void {
680
+ private updateSize(): void {
640
681
  this.assertBuilt('updateSize');
641
682
  if (!this.dom.root) return;
642
683
  this.width = this.dom.root.clientWidth || this.dom.root.offsetWidth;
643
684
  }
644
685
 
645
- refreshSlides(): void {
686
+ refresh(): this {
687
+ this.assertBuilt('refresh');
688
+ this.updateSize();
689
+ this.setupStyles();
690
+ this.setTrackIndex(this.trackIndexForRealIndex(this.state.index), false);
691
+ if (this.props.lazyload) this.loadImages();
692
+ if (this.props.autoplay) this.resume();
693
+ else this.pause();
694
+ return this;
695
+ }
696
+
697
+ private refreshSlides(): void {
646
698
  if (!this.dom.wrapper) return;
647
699
  this.dom.slides = all<HTMLElement>('[data-swiper-slide]', this.dom.wrapper);
648
700
  this.runtime.realCount = this.dom.slides.filter(
@@ -650,7 +702,7 @@ export class Swiper extends Component<
650
702
  ).length;
651
703
  }
652
704
 
653
- initLoop(): void {
705
+ private initLoop(): void {
654
706
  if (!this.dom.wrapper || this.dom.slides.length === 0) return;
655
707
 
656
708
  const first = this.dom.slides[0].cloneNode(true) as HTMLElement;
@@ -668,7 +720,7 @@ export class Swiper extends Component<
668
720
  this.refreshSlides();
669
721
  }
670
722
 
671
- setupStyles(): void {
723
+ private setupStyles(): void {
672
724
  if (!this.dom.root) return;
673
725
  this.dom.root.style.setProperty(
674
726
  '--swiper-slide-width',
@@ -676,7 +728,7 @@ export class Swiper extends Component<
676
728
  );
677
729
  }
678
730
 
679
- reInitView(): void {
731
+ private reInitView(): void {
680
732
  if (!this.dom.wrapper) return;
681
733
  this.pause();
682
734
  this.clearImageCleanups();
@@ -710,7 +762,7 @@ export class Swiper extends Component<
710
762
  if (this.props.autoplay) this.play();
711
763
  }
712
764
 
713
- clearPagination(): void {
765
+ private clearPagination(): void {
714
766
  this.cleanup.bindings?.();
715
767
  this.cleanup.bindings = null;
716
768
  this.dom.bullets.forEach((_, index) => {
@@ -727,7 +779,7 @@ export class Swiper extends Component<
727
779
  }
728
780
  }
729
781
 
730
- clearNavigation(): void {
782
+ private clearNavigation(): void {
731
783
  this.cleanup.navBindings?.();
732
784
  this.cleanup.navBindings = null;
733
785
  this.cleanup.events.off('nav:prev');
@@ -744,7 +796,7 @@ export class Swiper extends Component<
744
796
  }
745
797
  }
746
798
 
747
- bindEvents(): void {
799
+ private bindEvents(): void {
748
800
  const root = this.dom.root;
749
801
  const wrapper = this.dom.wrapper;
750
802
  if (!root || !wrapper) return;
@@ -830,7 +882,7 @@ export class Swiper extends Component<
830
882
  this.onTransitionEnd(event)
831
883
  );
832
884
  this.cleanup.events.on('window:resize', window, 'resize', () =>
833
- this.update(null, { force: true })
885
+ this.refresh()
834
886
  );
835
887
  this.cleanup.events.on('root:mouseenter', root, 'mouseenter', () =>
836
888
  this.pause()
@@ -840,7 +892,7 @@ export class Swiper extends Component<
840
892
  );
841
893
  }
842
894
 
843
- onStart(point: SwipePoint, target: EventTarget | null = null): void {
895
+ private onStart(point: SwipePoint, target: EventTarget | null = null): void {
844
896
  if (this.state.animating) return;
845
897
 
846
898
  this.runtime.logs = [];
@@ -855,7 +907,7 @@ export class Swiper extends Component<
855
907
  if (this.dom.wrapper) this.dom.wrapper.style.transition = 'none';
856
908
  }
857
909
 
858
- onMove(point: SwipePoint, event: Event): void {
910
+ private onMove(point: SwipePoint, event: Event): void {
859
911
  if (
860
912
  !this.runtime.touching ||
861
913
  this.runtime.scrolling ||
@@ -894,7 +946,7 @@ export class Swiper extends Component<
894
946
  }
895
947
  }
896
948
 
897
- onEnd(): void {
949
+ private onEnd(): void {
898
950
  if (!this.runtime.touching) return;
899
951
 
900
952
  this.runtime.touching = false;
@@ -932,7 +984,7 @@ export class Swiper extends Component<
932
984
  this.resume();
933
985
  }
934
986
 
935
- resetDrag(animate = true): void {
987
+ private resetDrag(animate = true): void {
936
988
  const target =
937
989
  this.state.trackIndex === 0
938
990
  ? 0
@@ -949,7 +1001,7 @@ export class Swiper extends Component<
949
1001
  this.resume();
950
1002
  }
951
1003
 
952
- onTransitionEnd(event?: Event): void {
1004
+ private onTransitionEnd(event?: Event): void {
953
1005
  if (
954
1006
  event &&
955
1007
  (event.target !== this.dom.wrapper ||
@@ -971,7 +1023,7 @@ export class Swiper extends Component<
971
1023
  }
972
1024
  }
973
1025
 
974
- pushLog(point: SwipePoint): void {
1026
+ private pushLog(point: SwipePoint): void {
975
1027
  this.runtime.logs.push({
976
1028
  x: point.pageX ?? point.clientX ?? 0,
977
1029
  y: point.pageY ?? point.clientY ?? 0,
@@ -980,7 +1032,7 @@ export class Swiper extends Component<
980
1032
  if (this.runtime.logs.length > 5) this.runtime.logs.shift();
981
1033
  }
982
1034
 
983
- getDuration(): number {
1035
+ private getDuration(): number {
984
1036
  if (this.runtime.logs.length === 0) return 0;
985
1037
  return (
986
1038
  this.runtime.logs[this.runtime.logs.length - 1].time -
@@ -988,7 +1040,7 @@ export class Swiper extends Component<
988
1040
  );
989
1041
  }
990
1042
 
991
- getOffset(): { x: number; y: number } {
1043
+ private getOffset(): { x: number; y: number } {
992
1044
  if (this.runtime.logs.length === 0) return { x: 0, y: 0 };
993
1045
  const first = this.runtime.logs[0];
994
1046
  const last = this.runtime.logs[this.runtime.logs.length - 1];
@@ -1009,7 +1061,10 @@ export class Swiper extends Component<
1009
1061
  return this.props.loop && this.realCount > 1 ? target + 1 : target;
1010
1062
  }
1011
1063
 
1012
- setTrackIndex(trackIndex: number, animate: boolean | null = true): void {
1064
+ private setTrackIndex(
1065
+ trackIndex: number,
1066
+ animate: boolean | null = true
1067
+ ): void {
1013
1068
  const target = normalizeNumber(trackIndex);
1014
1069
  this.setState({
1015
1070
  trackIndex: target,
@@ -1055,7 +1110,7 @@ export class Swiper extends Component<
1055
1110
  this.slideToTrack(this.state.trackIndex - 1);
1056
1111
  }
1057
1112
 
1058
- render(animate: boolean): void {
1113
+ private render(animate: boolean): void {
1059
1114
  this.assertBuilt('render');
1060
1115
  if (!this.dom.wrapper) return;
1061
1116
  if (animate) {
@@ -1069,7 +1124,7 @@ export class Swiper extends Component<
1069
1124
  this.dom.wrapper.style.transform = `translate3d(${this.state.transform}px, 0, 0)`;
1070
1125
  }
1071
1126
 
1072
- loadImages(): void {
1127
+ private loadImages(): void {
1073
1128
  this.assertBuilt('loadImages');
1074
1129
  const indices = [this.state.trackIndex];
1075
1130
  if (this.state.trackIndex > 0) indices.push(this.state.trackIndex - 1);
@@ -1104,17 +1159,17 @@ export class Swiper extends Component<
1104
1159
  });
1105
1160
  }
1106
1161
 
1107
- clearImageCleanups(): void {
1162
+ private clearImageCleanups(): void {
1108
1163
  this.runtime.imageCleanups?.forEach((cleanup) => cleanup());
1109
1164
  this.runtime.imageCleanups?.clear();
1110
1165
  }
1111
1166
 
1112
- initPagination(): void {
1167
+ private initPagination(): void {
1113
1168
  if (!this.dom.root) return;
1114
1169
 
1115
1170
  if (!this.dom.pagination) {
1116
1171
  this.dom.pagination = h('div', {
1117
- className: classList(
1172
+ className: joinClasses(
1118
1173
  this.props.className.pagination,
1119
1174
  this.props.className.paginationHorizontal,
1120
1175
  this.props.className.paginationClickable,
@@ -1130,7 +1185,7 @@ export class Swiper extends Component<
1130
1185
  this.props.className.paginationClickable,
1131
1186
  this.props.className.paginationBulletGroup
1132
1187
  );
1133
- setDataMarker(this.dom.pagination, 'data-swiper-pagination');
1188
+ this.dom.pagination.setAttribute('data-swiper-pagination', '');
1134
1189
  }
1135
1190
 
1136
1191
  const pagination = this.dom.pagination;
@@ -1142,7 +1197,7 @@ export class Swiper extends Component<
1142
1197
  for (let index = 0; index < this.realCount; index++) {
1143
1198
  const bullet = h('button', {
1144
1199
  type: 'button',
1145
- className: classList(
1200
+ className: joinClasses(
1146
1201
  this.props.className.indicator,
1147
1202
  this.props.className.bullet
1148
1203
  ),
@@ -1181,7 +1236,7 @@ export class Swiper extends Component<
1181
1236
  });
1182
1237
  }
1183
1238
 
1184
- initNavigation(): void {
1239
+ private initNavigation(): void {
1185
1240
  const prevButton = this.ensureNavigation('prev', 'arrow-left');
1186
1241
  const nextButton = this.ensureNavigation('next', 'arrow-right');
1187
1242
  this.dom.prevButton = prevButton;
@@ -1213,7 +1268,7 @@ export class Swiper extends Component<
1213
1268
  }
1214
1269
  }
1215
1270
 
1216
- ensureNavigation(
1271
+ private ensureNavigation(
1217
1272
  direction: SwiperDirection,
1218
1273
  iconName: 'arrow-left' | 'arrow-right'
1219
1274
  ): HTMLButtonElement {
@@ -1227,7 +1282,7 @@ export class Swiper extends Component<
1227
1282
  direction === 'prev'
1228
1283
  ? this.props.className.prev
1229
1284
  : this.props.className.next;
1230
- const className = classList(
1285
+ const className = joinClasses(
1231
1286
  this.props.className.navigation,
1232
1287
  directionClass
1233
1288
  );
@@ -1249,10 +1304,10 @@ export class Swiper extends Component<
1249
1304
  button.setAttribute('role', 'button');
1250
1305
  button.setAttribute('tabindex', '0');
1251
1306
  button.classList.add(this.props.className.navigation, directionClass);
1252
- setDataMarker(button, 'data-swiper-navigation', direction);
1307
+ button.setAttribute('data-swiper-navigation', direction);
1253
1308
  } else {
1254
1309
  button.classList.add(this.props.className.navigation, directionClass);
1255
- setDataMarker(button, 'data-swiper-navigation', direction);
1310
+ button.setAttribute('data-swiper-navigation', direction);
1256
1311
  }
1257
1312
 
1258
1313
  button.setAttribute(
@@ -1291,111 +1346,46 @@ export class Swiper extends Component<
1291
1346
  if (this.props.autoplay) this.play();
1292
1347
  }
1293
1348
 
1294
- update(
1295
- propsPatch: SwiperOptions | null | undefined = {},
1296
- { force = false }: SwiperUpdateOptions = {}
1297
- ): this {
1298
- if (this.runtime.destroyed)
1299
- throw new Error('Component.update: instance destroyed');
1300
- this.assertBuilt('update');
1301
-
1302
- const patch = isObjectPatch(propsPatch) ? propsPatch : {};
1303
- const nextProps = normalizeOptions(Object.assign({}, this.props, patch));
1304
- const normalizedPatch: Partial<ResolvedSwiperOptions> = {};
1305
-
1306
- Object.keys(patch).forEach((key) => {
1307
- const propKey = key as keyof ResolvedSwiperOptions;
1308
- normalizedPatch[propKey] = nextProps[propKey];
1309
- });
1310
-
1311
- this.props = nextProps;
1312
- this.emit('beforeUpdate', normalizedPatch, { force });
1313
- this.onUpdate(normalizedPatch, { force });
1314
- this.emit('afterUpdate', normalizedPatch, { force });
1315
- return this;
1316
- }
1317
-
1318
- protected onUpdate(
1319
- propsPatch: Partial<ResolvedSwiperOptions> | null | undefined = {},
1320
- _options: Required<ComponentUpdateOptions> = { force: false }
1321
- ): void {
1322
- if (
1323
- propsPatch &&
1324
- Object.prototype.hasOwnProperty.call(propsPatch, 'data')
1325
- ) {
1326
- this.updateData(propsPatch.data);
1327
- return;
1349
+ protected override normalizeStatePatch(
1350
+ patch: Partial<SwiperState>
1351
+ ): Partial<SwiperState> {
1352
+ const nextPatch = { ...patch };
1353
+ if (Object.hasOwn(nextPatch, 'data') && Array.isArray(nextPatch.data)) {
1354
+ nextPatch.data = cloneData(nextPatch.data);
1328
1355
  }
1356
+ return nextPatch;
1357
+ }
1329
1358
 
1330
- const needsReInit = ['loop', 'pagination', 'navigation', 'lazyload'].some(
1331
- (key) => Object.prototype.hasOwnProperty.call(propsPatch, key)
1359
+ protected override validateStatePatch(patch: Partial<SwiperState>): void {
1360
+ validateParam(
1361
+ 'state',
1362
+ patch,
1363
+ {
1364
+ type: 'plainObject',
1365
+ },
1366
+ 'Swiper.setState'
1332
1367
  );
1333
1368
 
1334
- if (needsReInit) {
1335
- this.reInitView();
1336
- return;
1337
- }
1338
-
1339
- this.updateSize();
1340
- this.setupStyles();
1341
- this.setTrackIndex(this.trackIndexForRealIndex(this.state.index), false);
1342
-
1343
- if (
1344
- Object.prototype.hasOwnProperty.call(propsPatch, 'autoplay') ||
1345
- Object.prototype.hasOwnProperty.call(propsPatch, 'delay')
1346
- ) {
1347
- this.restartAutoplay();
1348
- } else if (this.props.autoplay) this.resume();
1349
- else this.pause();
1350
- }
1351
-
1352
- updateData(data: SwiperDataItem[] | null = this.props.data): this {
1353
- if (this.runtime.destroyed)
1354
- throw new Error('Swiper.updateData: instance destroyed');
1355
- this.assertBuilt('updateData');
1356
- if (!this.dom.createdRoot)
1357
- throw new Error(
1358
- 'Swiper.updateData: not supported on DOM-bound instances.'
1369
+ for (const key of Object.keys(patch)) {
1370
+ if (!Object.hasOwn(SWIPER_STATE_SCHEMA, key)) {
1371
+ throw new Error(
1372
+ `Swiper.setState: "${key}" is not a supported state key.`
1373
+ );
1374
+ }
1375
+ validateParam(
1376
+ key,
1377
+ patch[key as keyof SwiperState],
1378
+ SWIPER_STATE_SCHEMA[key as keyof typeof SWIPER_STATE_SCHEMA],
1379
+ 'Swiper.setState'
1359
1380
  );
1360
-
1361
- validateParam('data', data, SWIPER_OPTIONS_SCHEMA.data, 'Swiper.options');
1362
- if (!Array.isArray(data)) {
1363
- this.props.data = data;
1364
- this.reInitView();
1365
- return this;
1366
1381
  }
1367
1382
 
1368
- this.props.data = data;
1369
- const realIndex = this.state.index;
1370
- const wrapper = this.dom.wrapper;
1371
- if (!wrapper) return this;
1372
-
1373
- this.pause();
1374
- this.clearImageCleanups();
1375
- this.cleanup.bindings?.();
1376
- this.cleanup.bindings = null;
1377
- this.cleanup.navBindings?.();
1378
- this.cleanup.navBindings = null;
1379
- this.cleanup.events.clear();
1380
-
1381
- const items = this.normalizeData(data);
1382
- wrapper.textContent = '';
1383
- items.forEach((item, index) => {
1384
- wrapper.appendChild(this.createDataSlide(item, index));
1385
- });
1386
- this.refreshSlides();
1387
- this.runtime.realCount = this.dom.slides.length;
1388
- this.setState({
1389
- index: Math.min(realIndex, Math.max(0, this.realCount - 1)),
1390
- });
1391
- this.reInitView();
1392
- return this;
1383
+ if (Array.isArray(patch.data)) this.normalizeData(patch.data);
1393
1384
  }
1394
1385
  }
1395
1386
 
1396
- export function createSwiper(
1397
- container: DOMReference,
1398
- input: SwiperOptions = {}
1399
- ): Swiper {
1400
- return new Swiper(container, input);
1387
+ export type Swiper = SwiperComponent;
1388
+
1389
+ export function createSwiper(input: SwiperProps = {}): Swiper {
1390
+ return new SwiperComponent(input);
1401
1391
  }