vanilla-jui 1.5.11 → 1.5.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.5.11",
3
+ "version": "1.5.12",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "vanilla-create-storage": "^1.0.4",
65
- "vanilla-signal": "^1.1.14",
65
+ "vanilla-signal": "^1.1.15",
66
66
  "vanilla-signal-i18n": "^1.1.2"
67
67
  },
68
68
  "devDependencies": {
@@ -75,6 +75,12 @@ interface TrackItem {
75
75
  key: string;
76
76
  }
77
77
 
78
+ interface NormalizedItemCacheEntry {
79
+ item: NormalizedSwiperDataItem;
80
+ keys: string[];
81
+ values: unknown[];
82
+ }
83
+
78
84
  export interface SwiperSlideContext {
79
85
  swiper: Swiper;
80
86
  item: NormalizedSwiperDataItem;
@@ -291,6 +297,8 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
291
297
  }) as SwiperState;
292
298
  const events = createEventManager();
293
299
  const itemKeys = new WeakMap<object, string>();
300
+ const normalizedCache = new Map<string, NormalizedItemCacheEntry>();
301
+ const trackItemCache = new Map<string, TrackItem>();
294
302
  const imageCleanups = new Set<() => void>();
295
303
  let swiper!: Swiper;
296
304
  let wrapper: HTMLElement | null = null;
@@ -311,9 +319,31 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
311
319
  if (!itemKeys.has(item)) itemKeys.set(item, randomId());
312
320
  return itemKeys.get(item) as string;
313
321
  };
322
+ const readItemEntries = (
323
+ item: SwiperDataItem
324
+ ): { keys: string[]; values: unknown[] } => {
325
+ const keys = Object.keys(item);
326
+ return {
327
+ keys,
328
+ values: keys.map((key) => item[key]),
329
+ };
330
+ };
331
+ const sameItemEntries = (
332
+ entry: NormalizedItemCacheEntry | undefined,
333
+ keys: string[],
334
+ values: unknown[]
335
+ ): boolean => {
336
+ if (!entry || entry.keys.length !== keys.length) return false;
337
+ for (let index = 0; index < keys.length; index += 1) {
338
+ if (entry.keys[index] !== keys[index]) return false;
339
+ if (!Object.is(entry.values[index], values[index])) return false;
340
+ }
341
+ return true;
342
+ };
314
343
  const normalizeData = (
315
344
  data: SwiperDataItem[]
316
345
  ): NormalizedSwiperDataItem[] => {
346
+ const activeKeys = new Set<string>();
317
347
  const items = data.map((item, index) => {
318
348
  validateParam(
319
349
  String(index),
@@ -321,13 +351,26 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
321
351
  SWIPER_DATA_ITEM_RULE,
322
352
  'Swiper.state.data'
323
353
  );
324
- return {
354
+ const key = itemKey(item);
355
+ activeKeys.add(key);
356
+ const { keys, values } = readItemEntries(item);
357
+ const cached = normalizedCache.get(key);
358
+ if (cached && sameItemEntries(cached, keys, values)) {
359
+ cached.item.index = index;
360
+ return cached.item;
361
+ }
362
+ const normalized = {
325
363
  ...item,
326
364
  blank: item.blank !== false,
327
365
  index,
328
- key: itemKey(item),
366
+ key,
329
367
  };
368
+ normalizedCache.set(key, { item: normalized, keys, values });
369
+ return normalized;
330
370
  });
371
+ for (const key of normalizedCache.keys()) {
372
+ if (!activeKeys.has(key)) normalizedCache.delete(key);
373
+ }
331
374
  if (!items.some((item) => item.sort != null)) return items;
332
375
  return items.sort((a, b) => {
333
376
  if (a.sort == null && b.sort == null) return a.index - b.index;
@@ -336,31 +379,47 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
336
379
  return a.sort - b.sort || a.index - b.index;
337
380
  });
338
381
  };
382
+ const trackItem = (
383
+ item: NormalizedSwiperDataItem,
384
+ index: number,
385
+ clone: TrackItem['clone']
386
+ ): TrackItem => {
387
+ const key =
388
+ clone === null
389
+ ? `real:${item.key}:${Boolean(item.url)}`
390
+ : `clone:${clone}:${item.key}:${Boolean(item.url)}`;
391
+ const cached = trackItemCache.get(key);
392
+ if (cached && cached.item === item && cached.clone === clone) {
393
+ cached.index = index;
394
+ return cached;
395
+ }
396
+ const next = { item, index, clone, key } satisfies TrackItem;
397
+ trackItemCache.set(key, next);
398
+ return next;
399
+ };
339
400
  const createTrackItems = (items: NormalizedSwiperDataItem[]): TrackItem[] => {
340
- const real = items.map((item, index) => ({
341
- item,
342
- index,
343
- clone: null,
344
- key: `real:${item.key}:${Boolean(item.url)}`,
345
- })) satisfies TrackItem[];
346
- if (!props.loop || items.length <= 1) return real;
401
+ const activeKeys = new Set<string>();
402
+ const real = items.map((item, index) => {
403
+ const current = trackItem(item, index, null);
404
+ activeKeys.add(current.key);
405
+ return current;
406
+ });
407
+ if (!props.loop || items.length <= 1) {
408
+ for (const key of trackItemCache.keys()) {
409
+ if (!activeKeys.has(key)) trackItemCache.delete(key);
410
+ }
411
+ return real;
412
+ }
347
413
  const first = items[0];
348
414
  const last = items[items.length - 1];
349
- return [
350
- {
351
- item: last,
352
- index: items.length - 1,
353
- clone: 'last',
354
- key: `clone:last:${last.key}:${Boolean(last.url)}`,
355
- },
356
- ...real,
357
- {
358
- item: first,
359
- index: 0,
360
- clone: 'first',
361
- key: `clone:first:${first.key}:${Boolean(first.url)}`,
362
- },
363
- ];
415
+ const lastClone = trackItem(last, items.length - 1, 'last');
416
+ const firstClone = trackItem(first, 0, 'first');
417
+ activeKeys.add(lastClone.key);
418
+ activeKeys.add(firstClone.key);
419
+ for (const key of trackItemCache.keys()) {
420
+ if (!activeKeys.has(key)) trackItemCache.delete(key);
421
+ }
422
+ return [lastClone, ...real, firstClone];
364
423
  };
365
424
  const realCount = (): number => normalizedItems().length;
366
425
  const trackCount = (): number => trackItems().length;
@@ -373,6 +432,10 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
373
432
  if (index === trackCount() - 1) return 0;
374
433
  return index - 1;
375
434
  };
435
+ const toRealIndexFromTrackPosition = (index: number): number => {
436
+ if (!props.loop) return Math.max(0, index);
437
+ return toRealIndex(index);
438
+ };
376
439
  const trackIndexForRealIndex = (index: number): number => {
377
440
  const count = realCount();
378
441
  if (!count) return 0;
@@ -710,11 +773,22 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
710
773
  });
711
774
  };
712
775
 
713
- const slideView = (itemAccessor: () => TrackItem): HTMLElement => {
776
+ const slideView = (
777
+ itemAccessor: () => TrackItem,
778
+ trackIndexAccessor: () => number
779
+ ): HTMLElement => {
780
+ const realIndex = (): number =>
781
+ toRealIndexFromTrackPosition(trackIndexAccessor());
714
782
  const slideContent = () => {
715
- const { item, index } = itemAccessor();
716
- const context = { swiper, item, index };
783
+ const { item } = itemAccessor();
717
784
  if (item.children != null) {
785
+ const context = {
786
+ swiper,
787
+ item,
788
+ get index() {
789
+ return realIndex();
790
+ },
791
+ } as SwiperSlideContext;
718
792
  return asRenderable(
719
793
  typeof item.children === 'function'
720
794
  ? item.children(context)
@@ -742,12 +816,12 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
742
816
  };
743
817
  const commonProps = {
744
818
  className: props.className.slide,
745
- 'data-swiper-slide': () => String(itemAccessor().index),
819
+ 'data-swiper-slide': () => String(realIndex()),
746
820
  'data-swiper-index': () =>
747
- itemAccessor().clone ? null : String(itemAccessor().index),
821
+ itemAccessor().clone ? null : String(realIndex()),
748
822
  'data-clone': () => itemAccessor().clone,
749
823
  role: 'group',
750
- 'aria-label': () => `Slide ${itemAccessor().index + 1}`,
824
+ 'aria-label': () => `Slide ${realIndex() + 1}`,
751
825
  children: slideContent,
752
826
  };
753
827
  return itemAccessor().item.url
@@ -581,6 +581,7 @@
581
581
  padding: 0.75em 1em;
582
582
  border-bottom: 1px solid var(--content-border-subtle);
583
583
  text-align: left;
584
+ vertical-align: top;
584
585
  }
585
586
 
586
587
  .j-content th {
package/src/css/menu.css CHANGED
@@ -363,6 +363,7 @@
363
363
  overflow-y: auto;
364
364
  scrollbar-width: thin;
365
365
  scrollbar-color: var(--ui-border) transparent;
366
+ height: 100%;
366
367
  }
367
368
 
368
369
  .j-menu[data-menu-type='mobile'] .menu {
@@ -416,13 +417,13 @@
416
417
  width: 100%;
417
418
  visibility: hidden;
418
419
  opacity: 0;
419
- transform: translateX(80%);
420
+ /* transform: translateX(80%); */
420
421
  transition:
421
- transform 0.3s ease-in-out,
422
- opacity 0.3s ease-in-out,
423
- visibility 0.3s ease-in-out;
422
+ /* transform 0.3s ease-in-out, */
423
+ /* visibility 0.3s ease-in-out, */ opacity 0.3s ease-in-out;
424
424
  max-width: 100%;
425
425
  background: var(--ui-bg);
426
+ padding-block: 0;
426
427
  }
427
428
 
428
429
  .j-menu[data-menu-type='mobile'] .menu .sub-menu .menu-item:hover a {
@@ -440,9 +441,9 @@
440
441
  > .sub-menu {
441
442
  visibility: visible;
442
443
  opacity: 1;
443
- transform: translateX(0);
444
- height: 100%;
444
+ /* transform: translateX(0); */
445
445
  z-index: var(--z-submenu);
446
+ height: 100%;
446
447
  }
447
448
 
448
449
  /* bottom menu */
package/src/css/table.css CHANGED
@@ -29,6 +29,7 @@
29
29
  table.j-table th:not([align]),
30
30
  table.j-table td:not([align]) {
31
31
  text-align: var(--table-cell-text-align);
32
+ vertical-align: var(--table-cell-vertical-align);
32
33
  }
33
34
 
34
35
  table.j-table a {
@@ -49,6 +49,7 @@
49
49
  --sub-menu-border-width: 0;
50
50
  --sub-menu-item-hover-outline-width: 0;
51
51
  --menu-link-gap: var(--s2);
52
+ --sub-menu-box-shadow: none;
52
53
  }
53
54
 
54
55
  .j-menu[data-menu-type='bottom'] {
@@ -10,6 +10,7 @@
10
10
  --table-border-color: var(--ui-border);
11
11
  --table-cell-padding: 0.5em 0.75em;
12
12
  --table-cell-text-align: left;
13
+ --table-cell-vertical-align: top;
13
14
  --table-link-fg: var(--tone-solid);
14
15
  --table-striped-bg: var(--tone-soft);
15
16
  --table-hover-bg: var(--tone-soft);
@@ -1162,13 +1162,13 @@
1162
1162
  }
1163
1163
 
1164
1164
  .j-radius-round {
1165
- --radius-2xs: 9999px;
1166
- --radius-xs: 9999px;
1167
- --radius-sm: 9999px;
1168
- --radius-md: 9999px;
1169
- --radius-lg: 9999px;
1170
- --radius-xl: 9999px;
1171
- --radius-2xl: 9999px;
1165
+ --radius-2xs: 0.375rem;
1166
+ --radius-xs: 0.5rem;
1167
+ --radius-sm: 0.75rem;
1168
+ --radius-md: 1rem;
1169
+ --radius-lg: 999px;
1170
+ --radius-xl: 999px;
1171
+ --radius-2xl: 999px;
1172
1172
  }
1173
1173
 
1174
1174
  /* ============================================