@indielayer/ui 1.16.0 → 1.18.0

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 (156) hide show
  1. package/README.md +2 -2
  2. package/docs/assets/css/tailwind.css +6 -0
  3. package/docs/components/common/CodePreview.vue +14 -9
  4. package/docs/components/common/DocsFeatures.vue +41 -0
  5. package/docs/components/common/DocsHero.vue +216 -0
  6. package/docs/components/common/DocumentPage.vue +99 -112
  7. package/docs/components/common/ExampleBlocks.vue +157 -0
  8. package/docs/components/menu/DocsMenu.vue +3 -0
  9. package/docs/components/toolbar/Toolbar.vue +11 -2
  10. package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
  11. package/docs/components/toolbar/ToolbarSearch.vue +59 -62
  12. package/docs/composables/useDocMeta.ts +47 -0
  13. package/docs/icons.ts +28 -0
  14. package/docs/layouts/default.vue +1 -3
  15. package/docs/layouts/simple.vue +3 -1
  16. package/docs/main.ts +5 -0
  17. package/docs/pages/colors.vue +56 -47
  18. package/docs/pages/component/infiniteLoader/composable.vue +168 -0
  19. package/docs/pages/component/infiniteLoader/index.vue +36 -0
  20. package/docs/pages/component/infiniteLoader/usage.vue +161 -0
  21. package/docs/pages/component/select/size.vue +1 -1
  22. package/docs/pages/component/select/usage.vue +14 -7
  23. package/docs/pages/component/virtualGrid/index.vue +29 -0
  24. package/docs/pages/component/virtualGrid/usage.vue +20 -0
  25. package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
  26. package/docs/pages/component/virtualList/index.vue +36 -0
  27. package/docs/pages/component/virtualList/usage.vue +17 -0
  28. package/docs/pages/error.vue +5 -3
  29. package/docs/pages/icons.vue +64 -54
  30. package/docs/pages/index.vue +93 -82
  31. package/docs/pages/typography.vue +38 -28
  32. package/docs/router/index.ts +31 -3
  33. package/docs/search/components.json +1 -1
  34. package/docs/search/index.json +1 -0
  35. package/lib/components/container/theme/Container.base.theme.js +1 -1
  36. package/lib/components/divider/theme/Divider.base.theme.js +1 -1
  37. package/lib/components/input/Input.vue.js +23 -24
  38. package/lib/components/select/Select.vue.d.ts +16 -27
  39. package/lib/components/select/Select.vue.js +452 -345
  40. package/lib/components/table/Table.vue.js +1 -1
  41. package/lib/composables/useVirtualList.d.ts +1 -1
  42. package/lib/index.d.ts +1 -0
  43. package/lib/index.js +88 -76
  44. package/lib/index.umd.js +4 -4
  45. package/lib/install.js +15 -7
  46. package/lib/version.d.ts +1 -1
  47. package/lib/version.js +1 -1
  48. package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
  49. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
  50. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
  51. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
  52. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
  53. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
  54. package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
  55. package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
  56. package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
  57. package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
  58. package/lib/virtual/components/virtualList/VirtualList.vue.js +159 -0
  59. package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
  60. package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
  61. package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
  62. package/lib/virtual/components/virtualList/types.d.ts +115 -0
  63. package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
  64. package/lib/virtual/components/virtualList/useDynamicRowHeight.js +68 -0
  65. package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
  66. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
  67. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
  68. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
  69. package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
  70. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
  71. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
  72. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
  73. package/lib/virtual/core/createCachedBounds.d.ts +6 -0
  74. package/lib/virtual/core/createCachedBounds.js +55 -0
  75. package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
  76. package/lib/virtual/core/getEstimatedSize.js +22 -0
  77. package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
  78. package/lib/virtual/core/getOffsetForIndex.js +40 -0
  79. package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
  80. package/lib/virtual/core/getStartStopIndices.js +31 -0
  81. package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
  82. package/lib/virtual/core/types.d.ts +11 -0
  83. package/lib/virtual/core/useCachedBounds.d.ts +7 -0
  84. package/lib/virtual/core/useCachedBounds.js +18 -0
  85. package/lib/virtual/core/useIsRtl.d.ts +2 -0
  86. package/lib/virtual/core/useIsRtl.js +15 -0
  87. package/lib/virtual/core/useItemSize.d.ts +5 -0
  88. package/lib/virtual/core/useItemSize.js +27 -0
  89. package/lib/virtual/core/useVirtualizer.d.ts +33 -0
  90. package/lib/virtual/core/useVirtualizer.js +171 -0
  91. package/lib/virtual/index.d.ts +9 -0
  92. package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
  93. package/lib/virtual/types.d.ts +2 -0
  94. package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
  95. package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
  96. package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
  97. package/lib/virtual/utils/assert.d.ts +1 -0
  98. package/lib/virtual/utils/assert.js +7 -0
  99. package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
  100. package/lib/virtual/utils/getRTLOffsetType.js +13 -0
  101. package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
  102. package/lib/virtual/utils/getScrollbarSize.js +11 -0
  103. package/lib/virtual/utils/isRtl.d.ts +1 -0
  104. package/lib/virtual/utils/isRtl.js +12 -0
  105. package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
  106. package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
  107. package/lib/virtual/utils/shallowCompare.d.ts +1 -0
  108. package/lib/virtual/utils/shallowCompare.js +14 -0
  109. package/package.json +8 -3
  110. package/src/components/container/theme/Container.base.theme.ts +1 -1
  111. package/src/components/divider/theme/Divider.base.theme.ts +1 -1
  112. package/src/components/input/Input.vue +1 -2
  113. package/src/components/select/Select.vue +97 -20
  114. package/src/components/table/Table.vue +1 -1
  115. package/src/composables/useVirtualList.ts +1 -1
  116. package/src/index.ts +1 -0
  117. package/src/install.ts +9 -3
  118. package/src/version.ts +1 -1
  119. package/src/virtual/README.md +285 -0
  120. package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
  121. package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
  122. package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
  123. package/src/virtual/components/virtualGrid/types.ts +160 -0
  124. package/src/virtual/components/virtualList/VirtualList.test.ts +164 -0
  125. package/src/virtual/components/virtualList/VirtualList.vue +227 -0
  126. package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
  127. package/src/virtual/components/virtualList/types.ts +127 -0
  128. package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +197 -0
  129. package/src/virtual/components/virtualList/useDynamicRowHeight.ts +149 -0
  130. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
  131. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
  132. package/src/virtual/composables/infinite-loader/types.ts +36 -0
  133. package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
  134. package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
  135. package/src/virtual/core/createCachedBounds.ts +72 -0
  136. package/src/virtual/core/getEstimatedSize.ts +29 -0
  137. package/src/virtual/core/getOffsetForIndex.ts +90 -0
  138. package/src/virtual/core/getStartStopIndices.test.ts +45 -0
  139. package/src/virtual/core/getStartStopIndices.ts +71 -0
  140. package/src/virtual/core/types.ts +17 -0
  141. package/src/virtual/core/useCachedBounds.ts +21 -0
  142. package/src/virtual/core/useIsRtl.ts +25 -0
  143. package/src/virtual/core/useItemSize.ts +34 -0
  144. package/src/virtual/core/useVirtualizer.ts +294 -0
  145. package/src/virtual/index.ts +25 -0
  146. package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
  147. package/src/virtual/types.ts +3 -0
  148. package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
  149. package/src/virtual/utils/areArraysEqual.ts +13 -0
  150. package/src/virtual/utils/assert.ts +10 -0
  151. package/src/virtual/utils/getRTLOffsetType.ts +51 -0
  152. package/src/virtual/utils/getScrollbarSize.ts +24 -0
  153. package/src/virtual/utils/isRtl.ts +13 -0
  154. package/src/virtual/utils/parseNumericStyleValue.ts +21 -0
  155. package/src/virtual/utils/shallowCompare.ts +29 -0
  156. package/volar.d.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indielayer/ui",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "Indielayer UI Components with Tailwind CSS build for Vue 3",
5
5
  "author": {
6
6
  "name": "João Teixeira",
@@ -50,12 +50,13 @@
50
50
  "devDependencies": {
51
51
  "@indielayer/stylelint-config": "^1.0.0",
52
52
  "@rushstack/eslint-patch": "^1.3.2",
53
+ "@unhead/vue": "^2.0.19",
53
54
  "@tsconfig/node18": "^2.0.1",
54
55
  "@types/jsdom": "^21.1.1",
55
56
  "@types/node": "^18.16.18",
56
57
  "@vitejs/plugin-vue": "^4.2.3",
57
58
  "@vitejs/plugin-vue-jsx": "^3.0.1",
58
- "@vue/test-utils": "^2.4.0",
59
+ "@vue/test-utils": "^2.4.6",
59
60
  "@vue/tsconfig": "^0.4.0",
60
61
  "@vuepic/vue-datepicker": "^11.0.2",
61
62
  "@vueuse/core": "^11.1.0",
@@ -107,9 +108,13 @@
107
108
  "gen:types": "vue-tsc --declaration --emitDeclarationOnly -p tsconfig.vitest.json --composite false",
108
109
  "gen:version": "node .scripts/gen-version.cjs",
109
110
  "gen:search": "node .scripts/gen-search.cjs",
111
+ "gen:llms": "node .scripts/gen-llms.cjs",
112
+ "gen:sitemap": "node .scripts/gen-sitemap.cjs",
110
113
  "test": "pnpm test:unit",
114
+ "test:ci": "vitest run --environment jsdom",
111
115
  "test:unit": "vitest --environment jsdom",
112
116
  "typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
113
- "stylelint": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore"
117
+ "stylelint": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore",
118
+ "stylelint:fix": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore --fix"
114
119
  }
115
120
  }
@@ -5,7 +5,7 @@ const theme: ContainerTheme = {
5
5
  wrapper: ({ props }) => {
6
6
  const classes = ['px-4 md:px-6']
7
7
 
8
- if (!props.fluid) classes.push('max-w-screen-lg mx-auto')
8
+ if (!props.fluid) classes.push('max-w-screen-xl mx-auto')
9
9
 
10
10
  return classes
11
11
  },
@@ -4,7 +4,7 @@ const theme: DividerTheme = {
4
4
  classes: {
5
5
  wrapper: ({ props }) => `flex justify-center items-center ${props.vertical ? 'h-full flex-col' : 'w-full'}`,
6
6
 
7
- label: 'font-medium text-sm text-secondary-600 dark:text-secondary-300',
7
+ label: 'font-medium text-xs text-slate-400 dark:text-secondary-300',
8
8
 
9
9
  line: 'bg-secondary-200 dark:bg-secondary-700 flex-grow',
10
10
  },
@@ -152,14 +152,13 @@ defineExpose({ focus, blur, reset, validate, setError })
152
152
  ref="elRef"
153
153
  :class="[
154
154
  classes.input,
155
- type === 'password' ? 'pr-10' : '',
156
155
  // error
157
156
  errorInternal
158
157
  ? 'border-error-500 dark:border-error-400 focus:outline-error-500'
159
158
  : 'focus:outline-[color:var(--x-input-border)]',
160
159
  {
161
160
  '!pl-10': iconLeft || icon,
162
- '!pr-10': iconRight,
161
+ '!pr-10': iconRight || showPasswordToggle || showClearIcon,
163
162
  },
164
163
  ]"
165
164
  :disabled="disabled"
@@ -17,6 +17,9 @@ const selectProps = {
17
17
  type: String,
18
18
  default: 'Filter by...',
19
19
  },
20
+ filterablePrefix: Boolean,
21
+ filterableSuffix: Boolean,
22
+ hideSelectedOptionSlots: Boolean,
20
23
  virtualList: Boolean,
21
24
  virtualListOffsetTop: Number,
22
25
  virtualListOffsetBottom: Number,
@@ -26,7 +29,7 @@ const selectProps = {
26
29
  },
27
30
  virtualListOverscan: {
28
31
  type: Number,
29
- default: 5,
32
+ default: 10,
30
33
  },
31
34
  placement: String as PropType<PopoverPlacement>,
32
35
  }
@@ -78,7 +81,7 @@ import XInput from '../input/Input.vue'
78
81
 
79
82
  const props = defineProps(selectProps)
80
83
 
81
- const emit = defineEmits([...useInputtable.emits(), 'close'])
84
+ const emit = defineEmits([...useInputtable.emits(), 'close', 'open'])
82
85
 
83
86
  const internalMultiple = computed(() => props.multiple || props.multipleCheckbox)
84
87
 
@@ -110,12 +113,35 @@ const selected = computed<any | any[]>({
110
113
  },
111
114
  })
112
115
 
113
- const labelCache = computed(() => {
114
- if (!props.options) return new Map<SelectOption, string>()
116
+ const optionsByValue = computed(() => {
117
+ if (!props.options) return new Map<string | number, SelectOption>()
115
118
 
116
- return new Map(props.options.map((option) => [option, option.label.toLowerCase()]))
119
+ return new Map(props.options.map((option) => [option.value, option]))
117
120
  })
118
121
 
122
+ const filterCache = computed(() => {
123
+ if (!props.options) return new Map<SelectOption, { label: string; prefix?: string; suffix?: string; }>()
124
+
125
+ return new Map(props.options.map((option) => [
126
+ option,
127
+ {
128
+ label: option.label.toLowerCase(),
129
+ prefix: props.filterablePrefix && option.prefix ? option.prefix.toLowerCase() : undefined,
130
+ suffix: props.filterableSuffix && option.suffix ? option.suffix.toLowerCase() : undefined,
131
+ },
132
+ ]))
133
+ })
134
+
135
+ function matchesFilter(option: SelectOption, filterLower: string) {
136
+ const cached = filterCache.value.get(option)
137
+
138
+ if (!cached) return false
139
+
140
+ return cached.label.includes(filterLower)
141
+ || cached.prefix?.includes(filterLower)
142
+ || cached.suffix?.includes(filterLower)
143
+ }
144
+
119
145
  const internalOptions = computed(() => {
120
146
  if (!props.options || props.options.length === 0) return []
121
147
 
@@ -128,10 +154,9 @@ const internalOptions = computed(() => {
128
154
  : [],
129
155
  )
130
156
  const singleSelectedValue = !internalMultiple.value ? selected.value : null
131
- const cache = labelCache.value
132
157
 
133
158
  return props.options
134
- .filter((option) => !hasFilter || cache.get(option)?.includes(filterLower))
159
+ .filter((option) => !hasFilter || matchesFilter(option, filterLower))
135
160
  .map((option) => {
136
161
  const isActive = internalMultiple.value
137
162
  ? selectedSet.has(option.value)
@@ -189,6 +214,7 @@ watch(isOpen, (isOpenValue) => {
189
214
  })
190
215
  }, 50)
191
216
 
217
+ emit('open')
192
218
  } else {
193
219
  if (props.filterable) filter.value = ''
194
220
  emit('close')
@@ -264,7 +290,7 @@ function findSelectableIndex(start: number | undefined, direction = 'down') {
264
290
  }
265
291
 
266
292
  function handleOptionClick(value: string | number) {
267
- const option = props.options?.find((i) => i.value === value)
293
+ const option = getItem(value)
268
294
 
269
295
  if (!option || option.disabled) return
270
296
 
@@ -325,8 +351,14 @@ function handleRemove(e: Event, value: string) {
325
351
  }
326
352
  }
327
353
 
354
+ function getItem(value: string | number | []) {
355
+ if (Array.isArray(value)) return undefined
356
+
357
+ return optionsByValue.value.get(value)
358
+ }
359
+
328
360
  function getLabel(value: string | number | []) {
329
- const option = props.options?.find((i) => i.value === value)
361
+ const option = getItem(value)
330
362
 
331
363
  if (option) return option.label
332
364
 
@@ -541,15 +573,29 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
541
573
  :key="value"
542
574
  size="xs"
543
575
  removable
544
- :outlined="!(isDisabled || options?.find((i) => i.value === value)?.disabled)"
545
- :disabled="isDisabled || options?.find((i) => i.value === value)?.disabled"
576
+ :outlined="!(isDisabled || getItem(value)?.disabled)"
577
+ :disabled="isDisabled || getItem(value)?.disabled"
546
578
  :style="{ 'max-width': valueIndex === 0 && hiddenTagsCounterRef ? `calc(100% - ${hiddenTagsCounterRef.offsetWidth + 6 + 'px'})` : undefined }"
547
579
  @remove="(e: Event) => { handleRemove(e, value) }"
548
580
  >
549
- <template #prefix>
550
- <slot name="tag-prefix" :item="options?.find((i) => i.value === value)"></slot>
581
+ <template v-if="!hideSelectedOptionSlots">
582
+ <div class="flex items-center">
583
+ <span v-if="$slots.prefix || getItem(value)?.prefix" class="mr-2 shrink-0">
584
+ <slot name="prefix" :item="getItem(value)">{{ getItem(value)?.prefix }}</slot>
585
+ </span>
586
+
587
+ <span class="flex-1 truncate">
588
+ {{ getLabel(value) }}
589
+ </span>
590
+
591
+ <span v-if="$slots.suffix || getItem(value)?.suffix" class="ml-1 shrink-0">
592
+ <slot name="suffix" :item="getItem(value)">{{ getItem(value)?.suffix }}</slot>
593
+ </span>
594
+ </div>
595
+ </template>
596
+ <template v-else>
597
+ {{ getLabel(value) }}
551
598
  </template>
552
- {{ getLabel(value) }}
553
599
  </x-tag>
554
600
 
555
601
  <div
@@ -561,7 +607,24 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
561
607
  </div>
562
608
  </template>
563
609
  <template v-else-if="!internalMultiple && !isEmpty(selected) && getLabel(selected) !== ''">
564
- {{ getLabel(selected) }}
610
+ <template v-if="!hideSelectedOptionSlots">
611
+ <div class="flex items-center">
612
+ <span v-if="$slots.prefix || getItem(selected)?.prefix" class="mr-2 shrink-0">
613
+ <slot name="prefix" :item="getItem(selected)">{{ getItem(selected)?.prefix }}</slot>
614
+ </span>
615
+
616
+ <span class="flex-1 truncate">
617
+ {{ getLabel(selected) }}
618
+ </span>
619
+
620
+ <span v-if="$slots.suffix || getItem(selected)?.suffix" class="ml-1 shrink-0">
621
+ <slot name="suffix" :item="getItem(selected)">{{ getItem(selected)?.suffix }}</slot>
622
+ </span>
623
+ </div>
624
+ </template>
625
+ <template v-else>
626
+ {{ getLabel(selected) }}
627
+ </template>
565
628
  </template>
566
629
 
567
630
  <template v-else>
@@ -635,14 +698,28 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
635
698
  :key="value"
636
699
  size="xs"
637
700
  removable
638
- :outlined="!(isDisabled || options?.find((i) => i.value === value)?.disabled)"
639
- :disabled="isDisabled || options?.find((i) => i.value === value)?.disabled"
701
+ :outlined="!(isDisabled || getItem(value)?.disabled)"
702
+ :disabled="isDisabled || getItem(value)?.disabled"
640
703
  @remove="(e: Event) => { handleRemove(e, value) }"
641
704
  >
642
- <template #prefix>
643
- <slot name="tag-prefix" :item="options?.find((i) => i.value === value)"></slot>
705
+ <template v-if="!hideSelectedOptionSlots">
706
+ <div class="flex items-center">
707
+ <span v-if="$slots.prefix || getItem(value)?.prefix" class="mr-2 shrink-0">
708
+ <slot name="prefix" :item="getItem(value)">{{ getItem(value)?.prefix }}</slot>
709
+ </span>
710
+
711
+ <span class="flex-1 truncate">
712
+ {{ getLabel(value) }}
713
+ </span>
714
+
715
+ <span v-if="$slots.suffix || getItem(value)?.suffix" class="ml-1 shrink-0">
716
+ <slot name="suffix" :item="getItem(value)">{{ getItem(value)?.suffix }}</slot>
717
+ </span>
718
+ </div>
719
+ </template>
720
+ <template v-else>
721
+ {{ getLabel(value) }}
644
722
  </template>
645
- {{ getLabel(value) }}
646
723
  </x-tag>
647
724
  </x-popover-container>
648
725
  </template>
@@ -41,7 +41,7 @@ const tableProps = {
41
41
  },
42
42
  virtualListOverscan: {
43
43
  type: Number,
44
- default: 5,
44
+ default: 10,
45
45
  },
46
46
  keyProp: String,
47
47
  selectable: Boolean,
@@ -21,7 +21,7 @@ export type UseVirtualListOptions = {
21
21
  /**
22
22
  * the extra buffer items outside of the view area
23
23
  *
24
- * @default 5
24
+ * @default 10
25
25
  */
26
26
  overscan?: number;
27
27
  }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export { default as version } from './version'
3
3
  export * from './components'
4
4
  export * from './composables'
5
5
  export * from './themes'
6
+ export * from './virtual'
6
7
  export type { UITheme, ComponentThemes } from './theme'
7
8
 
8
9
  export { default as createUI, type UIOptions } from './create'
package/src/install.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import * as components from './components'
2
+ import { XVirtualGrid, XVirtualList, XInfiniteLoader } from './virtual'
2
3
  import create from './create'
3
4
 
4
5
  export default create({
5
- components: Object.keys(components).map(
6
- (key) => components[key as keyof object],
7
- ),
6
+ components: [
7
+ ...Object.keys(components).map(
8
+ (key) => components[key as keyof object],
9
+ ),
10
+ XVirtualList,
11
+ XVirtualGrid,
12
+ XInfiniteLoader,
13
+ ],
8
14
  })
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export default '1.16.0'
1
+ export default '1.18.0'
@@ -0,0 +1,285 @@
1
+ # Indielayer - Virtual Vue
2
+
3
+ Vue 3 port of [react-window](https://github.com/bvaughn/react-window) - Efficient virtualized list and grid components using the Composition API.
4
+
5
+ ## Overview
6
+
7
+ High-performance virtualized list and grid components for Vue 3 applications. It renders only the visible items in large datasets, dramatically improving performance when dealing with thousands of rows or cells.
8
+
9
+ ## Quick Start
10
+
11
+ ### List Example
12
+
13
+ ```vue
14
+ <script setup lang="ts">
15
+ import { XVirtualList } from '@indielayer/ui'
16
+
17
+ const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
18
+ </script>
19
+
20
+ <template>
21
+ <XVirtualList
22
+ :row-count="items.length"
23
+ :row-height="50"
24
+ :style="{ height: '400px' }"
25
+ >
26
+ <template #row="{ index, style }">
27
+ <div :style="style" class="row">
28
+ {{ items[index] }}
29
+ </div>
30
+ </template>
31
+ </XVirtualList>
32
+ </template>
33
+
34
+ <style scoped>
35
+ .row {
36
+ display: flex;
37
+ align-items: center;
38
+ padding: 0 1rem;
39
+ border-bottom: 1px solid #eee;
40
+ }
41
+ </style>
42
+ ```
43
+
44
+ ### Grid Example
45
+
46
+ ```vue
47
+ <script setup lang="ts">
48
+ import { XVirtualGrid } from '@indielayer/ui'
49
+ </script>
50
+
51
+ <template>
52
+ <XVirtualGrid
53
+ :row-count="1000"
54
+ :column-count="1000"
55
+ :row-height="50"
56
+ :column-width="100"
57
+ :style="{ height: '500px', width: '800px' }"
58
+ >
59
+ <template #cell="{ rowIndex, columnIndex, style }">
60
+ <div :style="style" class="cell">
61
+ Row {{ rowIndex }}, Col {{ columnIndex }}
62
+ </div>
63
+ </template>
64
+ </XVirtualGrid>
65
+ </template>
66
+
67
+ <style scoped>
68
+ .cell {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ border: 1px solid #ddd;
73
+ }
74
+ </style>
75
+ ```
76
+
77
+ ## Features
78
+
79
+ - ⚡️ **Virtual scrolling** - Only renders visible items
80
+ - 📏 **Variable sizes** - Fixed, variable, or dynamic item sizes
81
+ - 📐 **Dynamic measurement** - Automatic height measurement with `useDynamicRowHeight`
82
+ - 🌐 **RTL support** - Right-to-left language support
83
+ - 🖥️ **SSR compatible** - Works with server-side rendering
84
+ - 📝 **TypeScript** - Full type definitions included
85
+ - 🎯 **Imperative API** - Programmatic scrolling via template refs
86
+ - ♿️ **ARIA support** - Accessibility attributes built-in
87
+ - 🔄 **Overscan** - Render extra items to reduce flicker
88
+ - ♾️ **Infinite loading** - Built-in support for infinite scroll/pagination
89
+
90
+ ## Components & Composables
91
+
92
+ ### List
93
+
94
+ Virtualized list component for rendering large datasets with many rows.
95
+
96
+ ```vue
97
+ <script setup lang="ts">
98
+ import { XVirtualList } from '@indielayer/ui'
99
+
100
+ const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
101
+ </script>
102
+
103
+ <template>
104
+ <XVirtualList
105
+ :row-count="items.length"
106
+ :row-height="50"
107
+ :style="{ height: '400px' }"
108
+ >
109
+ <template #row="{ index, style }">
110
+ <div :style="style">
111
+ {{ items[index] }}
112
+ </div>
113
+ </template>
114
+ </XVirtualList>
115
+ </template>
116
+ ```
117
+
118
+ ### Grid
119
+
120
+ Virtualized grid component for rendering data with rows and columns.
121
+
122
+ ```vue
123
+ <script setup lang="ts">
124
+ import { XVirtualGrid } from '@indielayer/ui'
125
+
126
+ const rowCount = 1000
127
+ const columnCount = 1000
128
+ </script>
129
+
130
+ <template>
131
+ <XVirtualGrid
132
+ :row-count="rowCount"
133
+ :column-count="columnCount"
134
+ :row-height="50"
135
+ :column-width="100"
136
+ :style="{ height: '400px', width: '600px' }"
137
+ >
138
+ <template #cell="{ rowIndex, columnIndex, style }">
139
+ <div :style="style">
140
+ Row {{ rowIndex }}, Col {{ columnIndex }}
141
+ </div>
142
+ </template>
143
+ </XVirtualGrid>
144
+ </template>
145
+ ```
146
+
147
+ ### Infinite Loader
148
+
149
+ Utility for loading data on-demand as users scroll through lists.
150
+
151
+ **Using the composable (recommended):**
152
+
153
+ ```vue
154
+ <script setup lang="ts">
155
+ import { ref } from 'vue'
156
+ import { XVirtualList, useInfiniteLoader } from '@indielayer/ui'
157
+
158
+ const items = ref<string[]>([])
159
+ const hasMoreData = ref(true)
160
+
161
+ const isRowLoaded = (index: number) => index < items.value.length
162
+
163
+ const loadMoreRows = async (startIndex: number, stopIndex: number) => {
164
+ // Simulate API call
165
+ await new Promise(resolve => setTimeout(resolve, 1000))
166
+
167
+ for (let i = startIndex; i <= stopIndex; i++) {
168
+ items.value[i] = `Item ${i}`
169
+ }
170
+
171
+ if (items.value.length >= 1000) {
172
+ hasMoreData.value = false
173
+ }
174
+ }
175
+
176
+ const { onRowsRendered } = useInfiniteLoader({
177
+ isRowLoaded,
178
+ loadMoreRows,
179
+ rowCount: hasMoreData.value ? items.value.length + 50 : items.value.length,
180
+ minimumBatchSize: 20,
181
+ threshold: 15,
182
+ })
183
+ </script>
184
+
185
+ <template>
186
+ <XVirtualList
187
+ :row-count="hasMoreData ? items.length + 50 : items.length"
188
+ :row-height="50"
189
+ :style="{ height: '400px' }"
190
+ :on-rows-rendered="onRowsRendered"
191
+ >
192
+ <template #row="{ index, style }">
193
+ <div :style="style">
194
+ {{ items[index] || 'Loading...' }}
195
+ </div>
196
+ </template>
197
+ </XVirtualList>
198
+ </template>
199
+ ```
200
+
201
+ **Using the component:**
202
+
203
+ ```vue
204
+ <script setup lang="ts">
205
+ import { ref } from 'vue'
206
+ import { XVirtualList, XInfiniteLoader } from '@indielayer/ui'
207
+
208
+ const items = ref<string[]>([])
209
+
210
+ const isRowLoaded = (index: number) => index < items.value.length
211
+
212
+ const loadMoreRows = async (startIndex: number, stopIndex: number) => {
213
+ for (let i = startIndex; i <= stopIndex; i++) {
214
+ items.value[i] = `Item ${i}`
215
+ }
216
+ }
217
+ </script>
218
+
219
+ <template>
220
+ <XInfiniteLoader
221
+ :is-row-loaded="isRowLoaded"
222
+ :load-more-rows="loadMoreRows"
223
+ :row-count="1000"
224
+ >
225
+ <template #default="{ onRowsRendered }">
226
+ <XVirtualList
227
+ :row-count="1000"
228
+ :row-height="50"
229
+ :style="{ height: '400px' }"
230
+ :on-rows-rendered="onRowsRendered"
231
+ >
232
+ <template #row="{ index, style }">
233
+ <div :style="style">
234
+ {{ items[index] || 'Loading...' }}
235
+ </div>
236
+ </template>
237
+ </XVirtualList>
238
+ </template>
239
+ </XInfiniteLoader>
240
+ </template>
241
+ ```
242
+
243
+ ## API
244
+
245
+ ### XVirtualList Props
246
+
247
+ | Prop | Type | Required | Description |
248
+ |------|------|----------|-------------|
249
+ | `rowCount` | `number` | ✅ | Number of rows in the list |
250
+ | `rowHeight` | `number \| string \| function` | ✅ | Row height (px, %, or function) |
251
+ | `style` | `CSSProperties` | ❌ | Container styles (should include height) |
252
+ | `rowProps` | `object` | ❌ | Additional props passed to row slot |
253
+ | `overscanCount` | `number` | ❌ | Extra rows to render (default: 3) |
254
+ | `defaultHeight` | `number` | ❌ | Default height for SSR |
255
+ | `onRowsRendered` | `function` | ❌ | Callback when visible rows change |
256
+ | `onResize` | `function` | ❌ | Callback when container resizes |
257
+
258
+ ### XVirtualGrid Props
259
+
260
+ | Prop | Type | Required | Description |
261
+ |------|------|----------|-------------|
262
+ | `rowCount` | `number` | ✅ | Number of rows |
263
+ | `columnCount` | `number` | ✅ | Number of columns |
264
+ | `rowHeight` | `number \| string \| function` | ✅ | Row height |
265
+ | `columnWidth` | `number \| string \| function` | ✅ | Column width |
266
+ | `style` | `CSSProperties` | ❌ | Container styles |
267
+ | `cellProps` | `object` | ❌ | Additional props passed to cell slot |
268
+ | `overscanCount` | `number` | ❌ | Extra rows/columns (default: 3) |
269
+ | `dir` | `'ltr' \| 'rtl' \| 'auto'` | ❌ | Text direction |
270
+ | `onCellsRendered` | `function` | ❌ | Callback when visible cells change |
271
+ | `onResize` | `function` | ❌ | Callback when container resizes |
272
+
273
+ ### Infinite Loader Props
274
+
275
+ | Prop | Type | Required | Default | Description |
276
+ |------|------|----------|---------|-------------|
277
+ | `isRowLoaded` | `(index: number) => boolean` | ✅ | - | Function to check if a row is loaded |
278
+ | `loadMoreRows` | `(start: number, stop: number) => Promise<void>` | ✅ | - | Async function to load more rows |
279
+ | `rowCount` | `number` | ✅ | - | Total row count (can be estimated) |
280
+ | `minimumBatchSize` | `number` | ❌ | 10 | Min rows to load per request |
281
+ | `threshold` | `number` | ❌ | 15 | Pre-fetch distance in rows |
282
+
283
+ ## Credits
284
+
285
+ This library is a Vue 3 port of [react-window](https://github.com/bvaughn/react-window) by Brian Vaughn. All credit for the original design and virtualization algorithms goes to the original author and contributors.