@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
@@ -0,0 +1,34 @@
1
+ import { assert } from '../utils/assert'
2
+ import type { SizeFunction } from './types'
3
+
4
+ export function useItemSize<Props extends object>({
5
+ containerSize,
6
+ itemSize: itemSizeProp,
7
+ }: {
8
+ containerSize: number;
9
+ itemSize: number | string | SizeFunction<Props>;
10
+ }) {
11
+ let itemSize: number | SizeFunction<Props>
12
+
13
+ switch (typeof itemSizeProp) {
14
+ case 'string': {
15
+ assert(
16
+ itemSizeProp.endsWith('%'),
17
+ `Invalid item size: "${itemSizeProp}"; string values must be percentages (e.g. "100%")`,
18
+ )
19
+ assert(
20
+ containerSize !== undefined,
21
+ 'Container size must be defined if a percentage item size is specified',
22
+ )
23
+
24
+ itemSize = (containerSize * parseInt(itemSizeProp)) / 100
25
+ break
26
+ }
27
+ default: {
28
+ itemSize = itemSizeProp
29
+ break
30
+ }
31
+ }
32
+
33
+ return itemSize
34
+ }
@@ -0,0 +1,294 @@
1
+ import {
2
+ computed,
3
+ isRef,
4
+ ref,
5
+ watch,
6
+ type ComputedRef,
7
+ type CSSProperties,
8
+ type Ref,
9
+ } from 'vue'
10
+ import { useEventListener, useResizeObserver } from '@vueuse/core'
11
+ import type { Align } from '../types'
12
+ import { parseNumericStyleValue } from '../utils/parseNumericStyleValue'
13
+ import { adjustScrollOffsetForRtl } from '../utils/adjustScrollOffsetForRtl'
14
+ import { shallowCompare } from '../utils/shallowCompare'
15
+ import { getEstimatedSize as getEstimatedSizeUtil } from './getEstimatedSize'
16
+ import { getOffsetForIndex } from './getOffsetForIndex'
17
+ import { getStartStopIndices as getStartStopIndicesUtil } from './getStartStopIndices'
18
+ import type { Direction, SizeFunction } from './types'
19
+ import { useCachedBounds } from './useCachedBounds'
20
+ import { useItemSize } from './useItemSize'
21
+
22
+ export function useVirtualizer<Props extends object>({
23
+ containerElement,
24
+ containerStyle,
25
+ defaultContainerSize = 0,
26
+ direction,
27
+ isRtl = false,
28
+ itemCount: itemCountInput,
29
+ itemProps: itemPropsInput,
30
+ itemSize: itemSizeProp,
31
+ onResize,
32
+ overscanCount,
33
+ }: {
34
+ containerElement: Ref<HTMLElement | null>;
35
+ containerStyle?: CSSProperties;
36
+ defaultContainerSize?: number;
37
+ direction: Direction;
38
+ isRtl?: boolean;
39
+ itemCount: number | Ref<number> | ComputedRef<number>;
40
+ itemProps: Props | ComputedRef<Props>;
41
+ itemSize: number | string | SizeFunction<Props> | Ref<number | string | SizeFunction<Props>> | ComputedRef<number | string | SizeFunction<Props>>;
42
+ onResize:
43
+ | ((
44
+ size: { height: number; width: number; },
45
+ prevSize: { height: number; width: number; }
46
+ ) => void)
47
+ | undefined;
48
+ overscanCount: number;
49
+ }) {
50
+ const itemCount = computed(() => {
51
+ return isRef(itemCountInput) ? itemCountInput.value : itemCountInput
52
+ })
53
+
54
+ const itemProps = computed(() => {
55
+ return isRef(itemPropsInput) ? itemPropsInput.value : itemPropsInput
56
+ })
57
+
58
+ const itemSizeNormalized = computed(() => {
59
+ return isRef(itemSizeProp) ? itemSizeProp.value : itemSizeProp
60
+ })
61
+
62
+ const styleHeight = computed(() => parseNumericStyleValue(containerStyle?.height))
63
+ const styleWidth = computed(() => parseNumericStyleValue(containerStyle?.width))
64
+
65
+ const sizeState = ref<{
66
+ height: number | undefined;
67
+ width: number | undefined;
68
+ }>({
69
+ height: direction === 'vertical' ? defaultContainerSize : undefined,
70
+ width: direction === 'horizontal' ? defaultContainerSize : undefined,
71
+ })
72
+
73
+ const observerDisabled = computed(
74
+ () =>
75
+ (direction === 'vertical' && styleHeight.value !== undefined) ||
76
+ (direction === 'horizontal' && styleWidth.value !== undefined),
77
+ )
78
+
79
+ useResizeObserver(
80
+ containerElement,
81
+ (entries) => {
82
+ if (observerDisabled.value) return
83
+
84
+ for (const entry of entries) {
85
+ const { contentRect } = entry
86
+ const prevState = sizeState.value
87
+
88
+ if (
89
+ prevState.height === contentRect.height &&
90
+ prevState.width === contentRect.width
91
+ ) {
92
+ return
93
+ }
94
+
95
+ sizeState.value = {
96
+ height: contentRect.height,
97
+ width: contentRect.width,
98
+ }
99
+ }
100
+ },
101
+ )
102
+
103
+ const size = computed(() => ({
104
+ height: styleHeight.value ?? sizeState.value.height,
105
+ width: styleWidth.value ?? sizeState.value.width,
106
+ }))
107
+
108
+ const prevSize = ref<{ height: number; width: number; }>({
109
+ height: 0,
110
+ width: 0,
111
+ })
112
+
113
+ watch(
114
+ size,
115
+ (newSize) => {
116
+ if (typeof onResize === 'function') {
117
+ const prev = prevSize.value
118
+ const height = newSize.height ?? 0
119
+ const width = newSize.width ?? 0
120
+
121
+ if (prev.height !== height || prev.width !== width) {
122
+ onResize({ height, width }, { ...prev })
123
+
124
+ prevSize.value = { height, width }
125
+ }
126
+ }
127
+ },
128
+ { immediate: true },
129
+ )
130
+
131
+ const containerSize = computed(() =>
132
+ direction === 'vertical'
133
+ ? size.value.height ?? defaultContainerSize
134
+ : size.value.width ?? defaultContainerSize,
135
+ )
136
+
137
+ const itemSize = computed(() =>
138
+ useItemSize({ containerSize: containerSize.value, itemSize: itemSizeNormalized.value }),
139
+ )
140
+
141
+ const cachedBounds = computed(() =>
142
+ useCachedBounds({
143
+ itemCount: itemCount.value,
144
+ itemProps: itemProps.value,
145
+ itemSize: itemSize.value,
146
+ }).value,
147
+ )
148
+
149
+ const getCellBounds = (index: number) => cachedBounds.value.get(index)
150
+
151
+ const indices = ref<{
152
+ startIndexVisible: number;
153
+ stopIndexVisible: number;
154
+ startIndexOverscan: number;
155
+ stopIndexOverscan: number;
156
+ }>(
157
+ getStartStopIndicesUtil({
158
+ cachedBounds: cachedBounds.value,
159
+ containerScrollOffset: 0,
160
+ containerSize: containerSize.value,
161
+ itemCount: itemCount.value,
162
+ overscanCount,
163
+ }),
164
+ )
165
+
166
+ const safeIndices = computed(() => {
167
+ const ind = indices.value
168
+
169
+ return {
170
+ startIndexVisible: Math.min(itemCount.value - 1, ind.startIndexVisible),
171
+ startIndexOverscan: Math.min(itemCount.value - 1, ind.startIndexOverscan),
172
+ stopIndexVisible: Math.min(itemCount.value - 1, ind.stopIndexVisible),
173
+ stopIndexOverscan: Math.min(itemCount.value - 1, ind.stopIndexOverscan),
174
+ }
175
+ })
176
+
177
+ const getEstimatedSize = computed(() =>
178
+ getEstimatedSizeUtil({
179
+ cachedBounds: cachedBounds.value,
180
+ itemCount: itemCount.value,
181
+ itemSize: itemSize.value,
182
+ }),
183
+ )
184
+
185
+ const getStartStopIndices = (scrollOffset: number) => {
186
+ const containerScrollOffset = adjustScrollOffsetForRtl({
187
+ containerElement: containerElement.value,
188
+ direction,
189
+ isRtl,
190
+ scrollOffset,
191
+ })
192
+
193
+ return getStartStopIndicesUtil({
194
+ cachedBounds: cachedBounds.value,
195
+ containerScrollOffset,
196
+ containerSize: containerSize.value,
197
+ itemCount: itemCount.value,
198
+ overscanCount,
199
+ })
200
+ }
201
+
202
+ watch(
203
+ [containerElement, () => direction, () => cachedBounds.value],
204
+ ([el]) => {
205
+ const scrollOffset =
206
+ (direction === 'vertical' ? el?.scrollTop : el?.scrollLeft) ?? 0
207
+
208
+ indices.value = getStartStopIndices(scrollOffset)
209
+ },
210
+ { immediate: true },
211
+ )
212
+
213
+ const onScroll = () => {
214
+ const el = containerElement.value
215
+
216
+ if (!el) return
217
+
218
+ const { scrollLeft, scrollTop } = el
219
+
220
+ const scrollOffset = adjustScrollOffsetForRtl({
221
+ containerElement: el,
222
+ direction,
223
+ isRtl,
224
+ scrollOffset: direction === 'vertical' ? scrollTop : scrollLeft,
225
+ })
226
+
227
+ const next = getStartStopIndicesUtil({
228
+ cachedBounds: cachedBounds.value,
229
+ containerScrollOffset: scrollOffset,
230
+ containerSize: containerSize.value,
231
+ itemCount: itemCount.value,
232
+ overscanCount,
233
+ })
234
+
235
+ if (!shallowCompare(next, indices.value)) {
236
+ indices.value = next
237
+ }
238
+ }
239
+
240
+ useEventListener(containerElement, 'scroll', onScroll)
241
+
242
+ const scrollToIndex = ({
243
+ align = 'auto',
244
+ containerScrollOffset,
245
+ index,
246
+ }: {
247
+ align?: Align;
248
+ containerScrollOffset: number;
249
+ index: number;
250
+ }) => {
251
+ let scrollOffset = getOffsetForIndex({
252
+ align,
253
+ cachedBounds: cachedBounds.value,
254
+ containerScrollOffset,
255
+ containerSize: containerSize.value,
256
+ index,
257
+ itemCount: itemCount.value,
258
+ itemSize: itemSize.value,
259
+ })
260
+
261
+ const el = containerElement.value
262
+
263
+ if (el) {
264
+ scrollOffset = adjustScrollOffsetForRtl({
265
+ containerElement: el,
266
+ direction,
267
+ isRtl,
268
+ scrollOffset,
269
+ })
270
+
271
+ if (typeof el.scrollTo !== 'function') {
272
+ const next = getStartStopIndices(scrollOffset)
273
+
274
+ if (!shallowCompare(indices.value, next)) {
275
+ indices.value = next
276
+ }
277
+ }
278
+
279
+ return scrollOffset
280
+ }
281
+
282
+ return undefined
283
+ }
284
+
285
+ return {
286
+ getCellBounds,
287
+ getEstimatedSize,
288
+ scrollToIndex,
289
+ startIndexOverscan: computed(() => safeIndices.value.startIndexOverscan),
290
+ startIndexVisible: computed(() => safeIndices.value.startIndexVisible),
291
+ stopIndexOverscan: computed(() => safeIndices.value.stopIndexOverscan),
292
+ stopIndexVisible: computed(() => safeIndices.value.stopIndexVisible),
293
+ }
294
+ }
@@ -0,0 +1,25 @@
1
+ export { default as XVirtualGrid } from './components/virtualGrid/VirtualGrid.vue'
2
+ export type {
3
+ CellSlotProps,
4
+ VirtualGridImperativeAPI,
5
+ VirtualGridProps,
6
+ } from './components/virtualGrid/types'
7
+
8
+ export { default as XVirtualList } from './components/virtualList/VirtualList.vue'
9
+ export { useDynamicRowHeight } from './components/virtualList/useDynamicRowHeight'
10
+ export type {
11
+ DynamicRowHeight,
12
+ VirtualListImperativeAPI,
13
+ VirtualListProps,
14
+ RowSlotProps,
15
+ } from './components/virtualList/types'
16
+
17
+ export { default as XInfiniteLoader } from './components/infiniteLoader/InfiniteLoader.vue'
18
+ export { useInfiniteLoader } from './composables/infinite-loader/useInfiniteLoader'
19
+ export type {
20
+ Indices,
21
+ OnRowsRendered,
22
+ InfiniteLoaderProps,
23
+ } from './composables/infinite-loader/types'
24
+
25
+ export { getScrollbarSize } from './utils/getScrollbarSize'
@@ -0,0 +1,162 @@
1
+ import EventEmitter from 'node:events'
2
+
3
+ type GetDOMRect = (element: HTMLElement) => DOMRectReadOnly | undefined | void;
4
+
5
+ const emitter = new EventEmitter()
6
+
7
+ emitter.setMaxListeners(100)
8
+
9
+ const elementToDOMRect = new Map<HTMLElement, DOMRect>()
10
+
11
+ let defaultDomRect: DOMRectReadOnly = new DOMRect(0, 0, 0, 0)
12
+ let disabled: boolean = false
13
+ let getDOMRect: GetDOMRect | undefined = undefined
14
+
15
+ export function disableResizeObserverForCurrentTest() {
16
+ disabled = true
17
+ }
18
+
19
+ export function setDefaultElementSize({
20
+ height,
21
+ width,
22
+ }: {
23
+ height: number;
24
+ width: number;
25
+ }) {
26
+ defaultDomRect = new DOMRect(0, 0, width, height)
27
+
28
+ emitter.emit('change')
29
+ }
30
+
31
+ export function setElementSizeFunction(value: GetDOMRect) {
32
+ getDOMRect = value
33
+
34
+ emitter.emit('change')
35
+ }
36
+
37
+ export function setElementSize({
38
+ element,
39
+ height,
40
+ width,
41
+ }: {
42
+ element: HTMLElement;
43
+ height: number;
44
+ width: number;
45
+ }) {
46
+ elementToDOMRect.set(element, new DOMRect(0, 0, width, height))
47
+
48
+ emitter.emit('change', element)
49
+ }
50
+
51
+ export function simulateUnsupportedEnvironmentForTest() {
52
+ // @ts-expect-error Simulate API being unsupported
53
+ window.ResizeObserver = null
54
+ }
55
+
56
+ export function mockResizeObserver() {
57
+ disabled = false
58
+
59
+ const originalResizeObserver = window.ResizeObserver
60
+
61
+ window.ResizeObserver = MockResizeObserver
62
+
63
+ return function unmockResizeObserver() {
64
+ window.ResizeObserver = originalResizeObserver
65
+
66
+ defaultDomRect = new DOMRect(0, 0, 0, 0)
67
+ disabled = false
68
+ getDOMRect = undefined
69
+
70
+ elementToDOMRect.clear()
71
+ }
72
+ }
73
+
74
+ class MockResizeObserver implements ResizeObserver {
75
+ readonly #callback: ResizeObserverCallback
76
+ #disconnected: boolean = false
77
+ #elements: Set<HTMLElement> = new Set()
78
+
79
+ constructor(callback: ResizeObserverCallback) {
80
+ this.#callback = callback
81
+
82
+ emitter.addListener('change', this.#onChange)
83
+ }
84
+
85
+ observe(element: HTMLElement) {
86
+ if (this.#disconnected) {
87
+ return
88
+ }
89
+
90
+ this.#elements.add(element)
91
+ this.#notify([element])
92
+ }
93
+
94
+ unobserve(element: HTMLElement) {
95
+ this.#elements.delete(element)
96
+ }
97
+
98
+ disconnect() {
99
+ this.#disconnected = true
100
+ this.#elements.clear()
101
+
102
+ emitter.removeListener('change', this.#onChange)
103
+ }
104
+
105
+ #notify(elements: HTMLElement[]) {
106
+ if (disabled) {
107
+ return
108
+ }
109
+
110
+ const entries = elements.map((element) => {
111
+ const computedStyle = window.getComputedStyle(element)
112
+ const writingMode = computedStyle.writingMode
113
+
114
+ let contentRect: DOMRectReadOnly =
115
+ elementToDOMRect.get(element) ?? defaultDomRect
116
+
117
+ if (getDOMRect) {
118
+ const contentRectOverride = getDOMRect(element)
119
+
120
+ if (contentRectOverride) {
121
+ contentRect = contentRectOverride
122
+ }
123
+ }
124
+
125
+ let blockSize = 0
126
+ let inlineSize = 0
127
+
128
+ if (writingMode.includes('vertical')) {
129
+ blockSize = contentRect.width
130
+ inlineSize = contentRect.height
131
+ } else {
132
+ blockSize = contentRect.height
133
+ inlineSize = contentRect.width
134
+ }
135
+
136
+ return {
137
+ borderBoxSize: [
138
+ {
139
+ blockSize,
140
+ inlineSize,
141
+ },
142
+ ],
143
+ contentBoxSize: [],
144
+ contentRect,
145
+ devicePixelContentBoxSize: [],
146
+ target: element,
147
+ }
148
+ })
149
+
150
+ this.#callback(entries, this)
151
+ }
152
+
153
+ #onChange = (target?: HTMLElement) => {
154
+ if (target) {
155
+ if (this.#elements.has(target)) {
156
+ this.#notify([target])
157
+ }
158
+ } else {
159
+ this.#notify(Array.from(this.#elements))
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,3 @@
1
+ export type Align = 'auto' | 'center' | 'end' | 'smart' | 'start';
2
+
3
+ export type TagNames = keyof HTMLElementTagNameMap;
@@ -0,0 +1,37 @@
1
+ import type { Direction } from '../core/types'
2
+ import { getRTLOffsetType } from './getRTLOffsetType'
3
+
4
+ export function adjustScrollOffsetForRtl({
5
+ containerElement,
6
+ direction,
7
+ isRtl,
8
+ scrollOffset,
9
+ }: {
10
+ containerElement: HTMLElement | null;
11
+ direction: Direction;
12
+ isRtl: boolean;
13
+ scrollOffset: number;
14
+ }) {
15
+ // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
16
+ // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
17
+ // So we need to determine which browser behavior we're dealing with, and mimic it.
18
+ if (direction === 'horizontal') {
19
+ if (isRtl) {
20
+ switch (getRTLOffsetType()) {
21
+ case 'negative': {
22
+ return -scrollOffset
23
+ }
24
+ case 'positive-descending': {
25
+ if (containerElement) {
26
+ const { clientWidth, scrollLeft, scrollWidth } = containerElement
27
+
28
+ return scrollWidth - clientWidth - scrollLeft
29
+ }
30
+ break
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ return scrollOffset
37
+ }
@@ -0,0 +1,13 @@
1
+ export function areArraysEqual(a: unknown[], b: unknown[]) {
2
+ if (a.length !== b.length) {
3
+ return false
4
+ }
5
+
6
+ for (let index = 0; index < a.length; index++) {
7
+ if (!Object.is(a[index], b[index])) {
8
+ return false
9
+ }
10
+ }
11
+
12
+ return true
13
+ }
@@ -0,0 +1,10 @@
1
+ export function assert(
2
+ expectedCondition: unknown,
3
+ message: string = 'Assertion error',
4
+ ): asserts expectedCondition {
5
+ if (!expectedCondition) {
6
+ console.error(message)
7
+
8
+ throw Error(message)
9
+ }
10
+ }
@@ -0,0 +1,51 @@
1
+ export type RTLOffsetType =
2
+ | 'negative'
3
+ | 'positive-descending'
4
+ | 'positive-ascending';
5
+
6
+ let cachedRTLResult: RTLOffsetType | null = null
7
+
8
+ // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
9
+ // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
10
+ // Safari's elastic bounce makes detecting this even more complicated wrt potential false positives.
11
+ // The safest way to check this is to intentionally set a negative offset,
12
+ // and then verify that the subsequent "scroll" event matches the negative offset.
13
+ // If it does not match, then we can assume a non-standard RTL scroll implementation.
14
+ export function getRTLOffsetType(recalculate: boolean = false): RTLOffsetType {
15
+ if (cachedRTLResult === null || recalculate) {
16
+ const outerDiv = document.createElement('div')
17
+ const outerStyle = outerDiv.style
18
+
19
+ outerStyle.width = '50px'
20
+ outerStyle.height = '50px'
21
+ outerStyle.overflow = 'scroll'
22
+ outerStyle.direction = 'rtl'
23
+
24
+ const innerDiv = document.createElement('div')
25
+ const innerStyle = innerDiv.style
26
+
27
+ innerStyle.width = '100px'
28
+ innerStyle.height = '100px'
29
+
30
+ outerDiv.appendChild(innerDiv)
31
+
32
+ document.body.appendChild(outerDiv)
33
+
34
+ if (outerDiv.scrollLeft > 0) {
35
+ cachedRTLResult = 'positive-descending'
36
+ } else {
37
+ outerDiv.scrollLeft = 1
38
+ if (outerDiv.scrollLeft === 0) {
39
+ cachedRTLResult = 'negative'
40
+ } else {
41
+ cachedRTLResult = 'positive-ascending'
42
+ }
43
+ }
44
+
45
+ document.body.removeChild(outerDiv)
46
+
47
+ return cachedRTLResult
48
+ }
49
+
50
+ return cachedRTLResult
51
+ }
@@ -0,0 +1,24 @@
1
+ let size: number = -1
2
+
3
+ export function getScrollbarSize(recalculate: boolean = false): number {
4
+ if (size === -1 || recalculate) {
5
+ const div = document.createElement('div')
6
+ const style = div.style
7
+
8
+ style.width = '50px'
9
+ style.height = '50px'
10
+ style.overflow = 'scroll'
11
+
12
+ document.body.appendChild(div)
13
+
14
+ size = div.offsetWidth - div.clientWidth
15
+
16
+ document.body.removeChild(div)
17
+ }
18
+
19
+ return size
20
+ }
21
+
22
+ export function setScrollbarSizeForTests(value: number) {
23
+ size = value
24
+ }
@@ -0,0 +1,13 @@
1
+ export function isRtl(element: HTMLElement) {
2
+ let currentElement: HTMLElement | null = element
3
+
4
+ while (currentElement) {
5
+ if (currentElement.dir) {
6
+ return currentElement.dir === 'rtl'
7
+ }
8
+
9
+ currentElement = currentElement.parentElement
10
+ }
11
+
12
+ return false
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { CSSProperties } from 'vue'
2
+
3
+ export function parseNumericStyleValue(
4
+ value: CSSProperties['height'],
5
+ ): number | undefined {
6
+ if (value !== undefined) {
7
+ switch (typeof value) {
8
+ case 'number': {
9
+ return value
10
+ }
11
+ case 'string': {
12
+ if (value.endsWith('px')) {
13
+ return parseFloat(value)
14
+ }
15
+ break
16
+ }
17
+ }
18
+ }
19
+
20
+ return undefined
21
+ }