vue-stack-tabs 0.1.12 → 0.1.13

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 (72) hide show
  1. package/{types → dist}/src/lib/hooks/useStackTab.d.ts +2 -2
  2. package/{types → dist}/src/lib/hooks/useTabpanel.d.ts +6 -2
  3. package/{types → dist}/src/lib/index.d.ts +5 -0
  4. package/{types → dist}/src/lib/model/TabModel.d.ts +2 -1
  5. package/dist/style.css +1 -1
  6. package/dist/vue-stack-tabs.es.js +2340 -2322
  7. package/dist/vue-stack-tabs.umd.js +10 -10
  8. package/package.json +3 -4
  9. package/src/lib/StackTabs.vue +136 -126
  10. package/src/lib/assets/style/common/mixins.scss +45 -45
  11. package/src/lib/assets/style/common/var.scss +65 -65
  12. package/src/lib/assets/style/icon.scss +93 -93
  13. package/src/lib/assets/style/index.scss +4 -4
  14. package/src/lib/assets/style/overlayer.scss +40 -40
  15. package/src/lib/assets/style/stackTab.scss +395 -395
  16. package/src/lib/assets/style/transition.scss +40 -40
  17. package/src/lib/banner.ts +51 -51
  18. package/src/lib/components/ContextMenu/ContextMenuItem.vue +15 -15
  19. package/src/lib/components/ContextMenu/index.vue +129 -129
  20. package/src/lib/components/OverlayLoading/Component.vue +182 -191
  21. package/src/lib/components/OverlayLoading/api.ts +31 -31
  22. package/src/lib/components/OverlayLoading/helpers.ts +31 -31
  23. package/src/lib/components/OverlayLoading/index.ts +12 -12
  24. package/src/lib/components/OverlayLoading/loaders/bars.vue +79 -79
  25. package/src/lib/components/OverlayLoading/loaders/dots.vue +92 -92
  26. package/src/lib/components/OverlayLoading/loaders/index.ts +9 -9
  27. package/src/lib/components/OverlayLoading/loaders/spinner.vue +41 -41
  28. package/src/lib/components/TabHeader/TabHeaderButton.vue +35 -35
  29. package/src/lib/components/TabHeader/TabHeaderItem.vue +63 -63
  30. package/src/lib/components/TabHeader/TabHeaderScroll.vue +240 -240
  31. package/src/lib/components/TabHeader/index.vue +135 -135
  32. package/src/lib/hooks/useContextMenu.ts +50 -50
  33. package/src/lib/hooks/useStackTab.ts +74 -65
  34. package/src/lib/hooks/useTabLoading.ts +34 -34
  35. package/src/lib/hooks/useTabRouter.ts +94 -94
  36. package/src/lib/hooks/useTabpanel.tsx +516 -491
  37. package/src/lib/i18n/index.ts +72 -72
  38. package/src/lib/i18n/lang/en.ts +15 -15
  39. package/src/lib/i18n/lang/zh-CN.ts +15 -15
  40. package/src/lib/iframe.vue +7 -7
  41. package/src/lib/index.ts +32 -24
  42. package/src/lib/model/TabModel.ts +178 -169
  43. package/src/lib/utils/TabIdHelper.ts +70 -70
  44. package/src/lib/utils/TabScrollHelper.ts +104 -104
  45. package/src/lib/utils/UriHelper.ts +22 -22
  46. /package/{types → dist}/index.d.ts +0 -0
  47. /package/{types → dist}/src/lib/StackTabs.vue.d.ts +0 -0
  48. /package/{types → dist}/src/lib/banner.d.ts +0 -0
  49. /package/{types → dist}/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +0 -0
  50. /package/{types → dist}/src/lib/components/ContextMenu/index.vue.d.ts +0 -0
  51. /package/{types → dist}/src/lib/components/OverlayLoading/Component.vue.d.ts +0 -0
  52. /package/{types → dist}/src/lib/components/OverlayLoading/api.d.ts +0 -0
  53. /package/{types → dist}/src/lib/components/OverlayLoading/helpers.d.ts +0 -0
  54. /package/{types → dist}/src/lib/components/OverlayLoading/index.d.ts +0 -0
  55. /package/{types → dist}/src/lib/components/OverlayLoading/loaders/bars.vue.d.ts +0 -0
  56. /package/{types → dist}/src/lib/components/OverlayLoading/loaders/dots.vue.d.ts +0 -0
  57. /package/{types → dist}/src/lib/components/OverlayLoading/loaders/index.d.ts +0 -0
  58. /package/{types → dist}/src/lib/components/OverlayLoading/loaders/spinner.vue.d.ts +0 -0
  59. /package/{types → dist}/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +0 -0
  60. /package/{types → dist}/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +0 -0
  61. /package/{types → dist}/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +0 -0
  62. /package/{types → dist}/src/lib/components/TabHeader/index.vue.d.ts +0 -0
  63. /package/{types → dist}/src/lib/hooks/useContextMenu.d.ts +0 -0
  64. /package/{types → dist}/src/lib/hooks/useTabLoading.d.ts +0 -0
  65. /package/{types → dist}/src/lib/hooks/useTabRouter.d.ts +0 -0
  66. /package/{types → dist}/src/lib/i18n/index.d.ts +0 -0
  67. /package/{types → dist}/src/lib/i18n/lang/en.d.ts +0 -0
  68. /package/{types → dist}/src/lib/i18n/lang/zh-CN.d.ts +0 -0
  69. /package/{types → dist}/src/lib/iframe.vue.d.ts +0 -0
  70. /package/{types → dist}/src/lib/utils/TabIdHelper.d.ts +0 -0
  71. /package/{types → dist}/src/lib/utils/TabScrollHelper.d.ts +0 -0
  72. /package/{types → dist}/src/lib/utils/UriHelper.d.ts +0 -0
@@ -1,240 +1,240 @@
1
- <template>
2
- <tab-header-button
3
- v-if="isButtonVisibled"
4
- icon-class="stack-tab__icon-left-arrow"
5
- :disabled="isDisabledLeftButton"
6
- @click="!isDisabledLeftButton && onButton(-1 * space)"
7
- />
8
- <div ref="headerScroll" class="stack-tab__scroll" @wheel.prevent="onWheel">
9
- <div
10
- ref="container"
11
- class="stack-tab__scroll-container"
12
- :class="{
13
- 'stack-tab__shadow-inset-left': !isDisabledLeftButton && isDisabledRightButton,
14
- 'stack-tab__shadow-inset-right': !isDisabledRightButton && isDisabledLeftButton,
15
- 'stack-tab__shadow-inset-all': !isDisabledLeftButton && !isDisabledRightButton
16
- }"
17
- @scroll.passive="update"
18
- >
19
- <slot />
20
- </div>
21
- <div
22
- v-show="hasScroller"
23
- ref="bar"
24
- class="stack-tab__scrollbar"
25
- :class="{ 'is-dragging': scrollDragging }"
26
- >
27
- <div
28
- ref="thumb"
29
- class="stack-tab__scrollbar-thumb"
30
- :class="{ 'is-dragging': scrollDragging }"
31
- :style="{
32
- width: `${thumbWidth}px`,
33
- transform: `translateX(${thumbLeft}px)`
34
- }"
35
- @mousedown.prevent="onDragStart"
36
- />
37
- </div>
38
- </div>
39
- <tab-header-button
40
- v-if="isButtonVisibled"
41
- icon-class="stack-tab__icon-right-arrow"
42
- :disabled="isDisabledRightButton"
43
- @click="!isDisabledRightButton && onButton(space!)"
44
- />
45
- </template>
46
-
47
- <script lang="ts" setup>
48
- import { computed, ref, reactive, onMounted, watch, onUnmounted } from 'vue'
49
- import { ResizeObserver } from '@juggle/resize-observer'
50
- import type { ScrollData, DragData } from '@/lib/model/TabModel'
51
- import { _scrollTo, _scrollIntoView } from '@/lib/utils/TabScrollHelper'
52
- import TabHeaderButton from '@/lib/components/TabHeader/TabHeaderButton.vue'
53
-
54
- // ref
55
- const container = ref<HTMLElement | null>(null)
56
- const bar = ref<HTMLElement | null>(null)
57
- const thumb = ref<HTMLElement | null>(null)
58
- const scrollDragging = ref<boolean>(false)
59
- const isDisabledLeftButton = ref<boolean>(false)
60
- const isDisabledRightButton = ref<boolean>(false)
61
- const isButtonVisibled = ref<boolean>(true)
62
- const headerScroll = ref<HTMLElement>()
63
- const props = withDefaults(
64
- defineProps<{
65
- // 每次移动距离
66
- space?: number
67
- isScrollWheel?: boolean
68
- isScrollButton?: boolean
69
- }>(),
70
- {
71
- space: 300,
72
- isScrollWheel: true,
73
- isScrollButton: true
74
- }
75
- )
76
- /**
77
- * 滚动条数据
78
- */
79
- const scrollData = reactive<ScrollData>({
80
- clientWidth: 0,
81
- scrollWidth: 0,
82
- scrollLeft: 0
83
- })
84
- const dragData = reactive<DragData>({
85
- thumbLeft: 0,
86
- startScrollLeft: 0,
87
- startThumbLeft: 0,
88
- startPageX: 0
89
- })
90
-
91
- // 监控滚动条数据用于启用/禁用按钮
92
- watch(scrollData, (newValue) => {
93
- setScrollButton(newValue)
94
- })
95
-
96
- const setScrollButton = (scrollData: ScrollData) => {
97
- isDisabledLeftButton.value = scrollData.scrollLeft <= 10
98
- isDisabledRightButton.value =
99
- scrollData.clientWidth + scrollData.scrollLeft - scrollData.scrollWidth >= -10
100
- isButtonVisibled.value = scrollData.scrollWidth !== scrollData.clientWidth
101
- }
102
-
103
- // 计算属性
104
- // 是否拥有滚动条
105
- const hasScroller = computed<boolean>(() => {
106
- return scrollData.scrollWidth > scrollData.clientWidth
107
- })
108
-
109
- // 滑块宽度
110
- const thumbWidth = computed<number>(() => {
111
- if (!hasScroller.value) return 0
112
-
113
- const { clientWidth, scrollWidth } = scrollData
114
- return (clientWidth / scrollWidth) * clientWidth
115
- })
116
-
117
- // 滑块 left
118
- const thumbLeft = computed(() => {
119
- if (!hasScroller.value) return 0
120
-
121
- const { clientWidth, scrollWidth, scrollLeft } = scrollData
122
-
123
- return (clientWidth - thumbWidth.value) * (scrollLeft / (scrollWidth - clientWidth))
124
- })
125
-
126
- // 方法
127
- // 更新滚动数据
128
- const update = () => {
129
- // 判断 container是否为空,主空直接返回
130
- if (container.value === null || container.value === undefined) {
131
- return
132
- }
133
- const { clientWidth, scrollWidth, scrollLeft } = container.value
134
-
135
- Object.assign(scrollData, { clientWidth, scrollWidth, scrollLeft })
136
- }
137
- const resizeUpdate = new ResizeObserver(() => {
138
- update()
139
- const cur = container.value?.querySelector('.stack-tab__item.is-active')
140
- if (cur) {
141
- if (!isInView(cur as HTMLElement)) {
142
- scrollIntoView(cur as HTMLElement)
143
- }
144
- }
145
- })
146
- const tabsUpdate = new ResizeObserver(() => {
147
- if (!hasScroller.value) {
148
- const cur = container.value?.querySelector('.stack-tab__item.is-active')
149
- if (cur) {
150
- if (!isInView(cur as HTMLElement)) {
151
- scrollIntoView(cur as HTMLElement)
152
- }
153
- }
154
- }
155
- update()
156
- })
157
- // 滚动到指定位置
158
- const scrollTo = (left: number, smooth = true) => {
159
- _scrollTo({ wrap: container.value, left, smooth })
160
- }
161
-
162
- const onButton = (space: number) => {
163
- scrollTo(container.value!.scrollLeft + space)
164
- }
165
- // 页签鼠标滚动
166
- const onWheel = (e: WheelEvent) => {
167
- const isWheelUp = e.deltaY < 0
168
- scrollTo(container.value!.scrollLeft + (isWheelUp ? -props.space : props.space))
169
- }
170
- // 拖拽
171
- const onDragStart = (e: MouseEvent) => {
172
- Object.assign(dragData, {
173
- startPageX: e.pageX,
174
- startScrollLeft: container.value?.scrollLeft,
175
- startThumbLeft: thumbLeft.value,
176
- thumbLeft: thumbLeft.value
177
- })
178
- scrollDragging.value = true
179
- document.addEventListener('mousemove', onDragMove)
180
- document.addEventListener('mouseup', onDragEnd)
181
- }
182
-
183
- // 拖拽移动
184
- const onDragMove = (e: MouseEvent) => {
185
- const { clientWidth, scrollWidth } = scrollData
186
- let thumbLeft = dragData.startThumbLeft + e.pageX - dragData.startPageX
187
- const maxThumbLeft = clientWidth - thumbWidth.value
188
-
189
- if (thumbLeft < 0) {
190
- thumbLeft = 0
191
- } else if (thumbLeft > maxThumbLeft) {
192
- thumbLeft = maxThumbLeft
193
- }
194
- // 更新滑块定位
195
- dragData.thumbLeft = thumbLeft
196
- // 滚动
197
- scrollTo((thumbLeft * (scrollWidth - clientWidth)) / (clientWidth - thumbWidth.value), false)
198
- e.preventDefault()
199
- }
200
- // 拖拽结束
201
- const onDragEnd = (e: MouseEvent) => {
202
- scrollDragging.value = false
203
- document.removeEventListener('mousemove', onDragMove)
204
- document.removeEventListener('mouseup', onDragEnd)
205
-
206
- e.preventDefault()
207
- }
208
- onMounted(() => {
209
- resizeUpdate.observe(headerScroll.value as Element)
210
- const tabNav = container.value?.querySelector('.stack-tab__nav')
211
- tabsUpdate.observe(tabNav!)
212
- })
213
- onUnmounted(() => {
214
- resizeUpdate.disconnect()
215
- tabsUpdate.disconnect()
216
- })
217
-
218
- // 滚动到元素
219
- const scrollIntoView = (el: HTMLElement) => {
220
- _scrollIntoView({ el, wrap: container, block: 'end', inline: 'center' })
221
- }
222
-
223
- // 判断子节点是否完全在可视区域
224
- const isInView = (el: HTMLElement) => {
225
- if (!hasScroller.value) {
226
- return true
227
- }
228
- const offsetLeft = el.offsetLeft
229
- const scrollLeft = container.value!.scrollLeft
230
-
231
- return !(
232
- offsetLeft < scrollLeft ||
233
- offsetLeft + el.clientWidth > scrollLeft + container.value!.clientWidth
234
- )
235
- }
236
-
237
- defineExpose({ scrollIntoView, isInView })
238
- </script>
239
-
240
- <style scoped></style>
1
+ <template>
2
+ <tab-header-button
3
+ v-if="isButtonVisibled"
4
+ icon-class="stack-tab__icon-left-arrow"
5
+ :disabled="isDisabledLeftButton"
6
+ @click="!isDisabledLeftButton && onButton(-1 * space)"
7
+ />
8
+ <div ref="headerScroll" class="stack-tab__scroll" @wheel.prevent="onWheel">
9
+ <div
10
+ ref="container"
11
+ class="stack-tab__scroll-container"
12
+ :class="{
13
+ 'stack-tab__shadow-inset-left': !isDisabledLeftButton && isDisabledRightButton,
14
+ 'stack-tab__shadow-inset-right': !isDisabledRightButton && isDisabledLeftButton,
15
+ 'stack-tab__shadow-inset-all': !isDisabledLeftButton && !isDisabledRightButton
16
+ }"
17
+ @scroll.passive="update"
18
+ >
19
+ <slot />
20
+ </div>
21
+ <div
22
+ v-show="hasScroller"
23
+ ref="bar"
24
+ class="stack-tab__scrollbar"
25
+ :class="{ 'is-dragging': scrollDragging }"
26
+ >
27
+ <div
28
+ ref="thumb"
29
+ class="stack-tab__scrollbar-thumb"
30
+ :class="{ 'is-dragging': scrollDragging }"
31
+ :style="{
32
+ width: `${thumbWidth}px`,
33
+ transform: `translateX(${thumbLeft}px)`
34
+ }"
35
+ @mousedown.prevent="onDragStart"
36
+ />
37
+ </div>
38
+ </div>
39
+ <tab-header-button
40
+ v-if="isButtonVisibled"
41
+ icon-class="stack-tab__icon-right-arrow"
42
+ :disabled="isDisabledRightButton"
43
+ @click="!isDisabledRightButton && onButton(space!)"
44
+ />
45
+ </template>
46
+
47
+ <script lang="ts" setup>
48
+ import { computed, ref, reactive, onMounted, watch, onUnmounted } from 'vue'
49
+ import { ResizeObserver } from '@juggle/resize-observer'
50
+ import type { ScrollData, DragData } from '@/lib/model/TabModel'
51
+ import { _scrollTo, _scrollIntoView } from '@/lib/utils/TabScrollHelper'
52
+ import TabHeaderButton from '@/lib/components/TabHeader/TabHeaderButton.vue'
53
+
54
+ // ref
55
+ const container = ref<HTMLElement | null>(null)
56
+ const bar = ref<HTMLElement | null>(null)
57
+ const thumb = ref<HTMLElement | null>(null)
58
+ const scrollDragging = ref<boolean>(false)
59
+ const isDisabledLeftButton = ref<boolean>(false)
60
+ const isDisabledRightButton = ref<boolean>(false)
61
+ const isButtonVisibled = ref<boolean>(true)
62
+ const headerScroll = ref<HTMLElement>()
63
+ const props = withDefaults(
64
+ defineProps<{
65
+ // 每次移动距离
66
+ space?: number
67
+ isScrollWheel?: boolean
68
+ isScrollButton?: boolean
69
+ }>(),
70
+ {
71
+ space: 300,
72
+ isScrollWheel: true,
73
+ isScrollButton: true
74
+ }
75
+ )
76
+ /**
77
+ * 滚动条数据
78
+ */
79
+ const scrollData = reactive<ScrollData>({
80
+ clientWidth: 0,
81
+ scrollWidth: 0,
82
+ scrollLeft: 0
83
+ })
84
+ const dragData = reactive<DragData>({
85
+ thumbLeft: 0,
86
+ startScrollLeft: 0,
87
+ startThumbLeft: 0,
88
+ startPageX: 0
89
+ })
90
+
91
+ // 监控滚动条数据用于启用/禁用按钮
92
+ watch(scrollData, (newValue) => {
93
+ setScrollButton(newValue)
94
+ })
95
+
96
+ const setScrollButton = (scrollData: ScrollData) => {
97
+ isDisabledLeftButton.value = scrollData.scrollLeft <= 10
98
+ isDisabledRightButton.value =
99
+ scrollData.clientWidth + scrollData.scrollLeft - scrollData.scrollWidth >= -10
100
+ isButtonVisibled.value = scrollData.scrollWidth !== scrollData.clientWidth
101
+ }
102
+
103
+ // 计算属性
104
+ // 是否拥有滚动条
105
+ const hasScroller = computed<boolean>(() => {
106
+ return scrollData.scrollWidth > scrollData.clientWidth
107
+ })
108
+
109
+ // 滑块宽度
110
+ const thumbWidth = computed<number>(() => {
111
+ if (!hasScroller.value) return 0
112
+
113
+ const { clientWidth, scrollWidth } = scrollData
114
+ return (clientWidth / scrollWidth) * clientWidth
115
+ })
116
+
117
+ // 滑块 left
118
+ const thumbLeft = computed(() => {
119
+ if (!hasScroller.value) return 0
120
+
121
+ const { clientWidth, scrollWidth, scrollLeft } = scrollData
122
+
123
+ return (clientWidth - thumbWidth.value) * (scrollLeft / (scrollWidth - clientWidth))
124
+ })
125
+
126
+ // 方法
127
+ // 更新滚动数据
128
+ const update = () => {
129
+ // 判断 container是否为空,主空直接返回
130
+ if (container.value === null || container.value === undefined) {
131
+ return
132
+ }
133
+ const { clientWidth, scrollWidth, scrollLeft } = container.value
134
+
135
+ Object.assign(scrollData, { clientWidth, scrollWidth, scrollLeft })
136
+ }
137
+ const resizeUpdate = new ResizeObserver(() => {
138
+ update()
139
+ const cur = container.value?.querySelector('.stack-tab__item.is-active')
140
+ if (cur) {
141
+ if (!isInView(cur as HTMLElement)) {
142
+ scrollIntoView(cur as HTMLElement)
143
+ }
144
+ }
145
+ })
146
+ const tabsUpdate = new ResizeObserver(() => {
147
+ if (!hasScroller.value) {
148
+ const cur = container.value?.querySelector('.stack-tab__item.is-active')
149
+ if (cur) {
150
+ if (!isInView(cur as HTMLElement)) {
151
+ scrollIntoView(cur as HTMLElement)
152
+ }
153
+ }
154
+ }
155
+ update()
156
+ })
157
+ // 滚动到指定位置
158
+ const scrollTo = (left: number, smooth = true) => {
159
+ _scrollTo({ wrap: container.value, left, smooth })
160
+ }
161
+
162
+ const onButton = (space: number) => {
163
+ scrollTo(container.value!.scrollLeft + space)
164
+ }
165
+ // 页签鼠标滚动
166
+ const onWheel = (e: WheelEvent) => {
167
+ const isWheelUp = e.deltaY < 0
168
+ scrollTo(container.value!.scrollLeft + (isWheelUp ? -props.space : props.space))
169
+ }
170
+ // 拖拽
171
+ const onDragStart = (e: MouseEvent) => {
172
+ Object.assign(dragData, {
173
+ startPageX: e.pageX,
174
+ startScrollLeft: container.value?.scrollLeft,
175
+ startThumbLeft: thumbLeft.value,
176
+ thumbLeft: thumbLeft.value
177
+ })
178
+ scrollDragging.value = true
179
+ document.addEventListener('mousemove', onDragMove)
180
+ document.addEventListener('mouseup', onDragEnd)
181
+ }
182
+
183
+ // 拖拽移动
184
+ const onDragMove = (e: MouseEvent) => {
185
+ const { clientWidth, scrollWidth } = scrollData
186
+ let thumbLeft = dragData.startThumbLeft + e.pageX - dragData.startPageX
187
+ const maxThumbLeft = clientWidth - thumbWidth.value
188
+
189
+ if (thumbLeft < 0) {
190
+ thumbLeft = 0
191
+ } else if (thumbLeft > maxThumbLeft) {
192
+ thumbLeft = maxThumbLeft
193
+ }
194
+ // 更新滑块定位
195
+ dragData.thumbLeft = thumbLeft
196
+ // 滚动
197
+ scrollTo((thumbLeft * (scrollWidth - clientWidth)) / (clientWidth - thumbWidth.value), false)
198
+ e.preventDefault()
199
+ }
200
+ // 拖拽结束
201
+ const onDragEnd = (e: MouseEvent) => {
202
+ scrollDragging.value = false
203
+ document.removeEventListener('mousemove', onDragMove)
204
+ document.removeEventListener('mouseup', onDragEnd)
205
+
206
+ e.preventDefault()
207
+ }
208
+ onMounted(() => {
209
+ resizeUpdate.observe(headerScroll.value as Element)
210
+ const tabNav = container.value?.querySelector('.stack-tab__nav')
211
+ tabsUpdate.observe(tabNav!)
212
+ })
213
+ onUnmounted(() => {
214
+ resizeUpdate.disconnect()
215
+ tabsUpdate.disconnect()
216
+ })
217
+
218
+ // 滚动到元素
219
+ const scrollIntoView = (el: HTMLElement) => {
220
+ _scrollIntoView({ el, wrap: container, block: 'end', inline: 'center' })
221
+ }
222
+
223
+ // 判断子节点是否完全在可视区域
224
+ const isInView = (el: HTMLElement) => {
225
+ if (!hasScroller.value) {
226
+ return true
227
+ }
228
+ const offsetLeft = el.offsetLeft
229
+ const scrollLeft = container.value!.scrollLeft
230
+
231
+ return !(
232
+ offsetLeft < scrollLeft ||
233
+ offsetLeft + el.clientWidth > scrollLeft + container.value!.clientWidth
234
+ )
235
+ }
236
+
237
+ defineExpose({ scrollIntoView, isInView })
238
+ </script>
239
+
240
+ <style scoped></style>