@rolder/kit 3.0.0-alpha.6 → 3.0.0-alpha.8
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 +3 -3
- package/rslib.config.ts +21 -0
- package/src/ai/ui/conversation/ConversationContext.ts +21 -0
- package/src/ai/ui/conversation/ConversationProvider.tsx +21 -0
- package/src/ai/ui/conversation/Empty.tsx +15 -0
- package/src/ai/ui/conversation/File.tsx +40 -0
- package/src/ai/ui/conversation/FileIcon.tsx +143 -0
- package/src/ai/ui/conversation/Loader.tsx +8 -0
- package/src/ai/ui/conversation/Message.tsx +34 -0
- package/src/ai/ui/conversation/Root.tsx +24 -0
- package/src/ai/ui/conversation/index.ts +16 -0
- package/{dist/ai/ui/conversation/types.d.ts → src/ai/ui/conversation/types.ts} +5 -4
- package/src/ai/ui/conversation/useChatMessage.ts +13 -0
- package/src/ai/ui/promptInput/File.tsx +98 -0
- package/src/ai/ui/promptInput/FileIcon.tsx +149 -0
- package/src/ai/ui/promptInput/Footer.tsx +5 -0
- package/src/ai/ui/promptInput/PromptInputContext.ts +24 -0
- package/src/ai/ui/promptInput/PromptInputProvider.tsx +54 -0
- package/src/ai/ui/promptInput/Root.tsx +29 -0
- package/src/ai/ui/promptInput/Submit.tsx +39 -0
- package/src/ai/ui/promptInput/Textarea.tsx +39 -0
- package/src/ai/ui/promptInput/index.ts +15 -0
- package/src/ai/ui/promptInput/types.ts +9 -0
- package/src/ai/utils/convertFileUIPartBlobToDataURL.ts +29 -0
- package/src/ai/utils/parseAiMessagePart.ts +19 -0
- package/src/app/AppDefaults.tsx +21 -0
- package/src/app/DefaultApp.tsx +50 -0
- package/src/app/cookieColorSchemeManager.ts +70 -0
- package/src/app/defaultRequestMiddlewares.ts +22 -0
- package/src/app/defaultTheme.ts +22 -0
- package/src/functions/getCookie.ts +36 -0
- package/src/functions/setCookie.ts +29 -0
- package/src/functions/setCookies.ts +24 -0
- package/src/hooks/useMutation.ts +14 -0
- package/src/hooks/useMutationWithInvalidate.ts +23 -0
- package/{dist/index.d.ts → src/index.ts} +45 -5
- package/{dist → src}/styles.css +21 -11
- package/src/surreal/connection.ts +72 -0
- package/src/surreal/deafaultCrud.ts +25 -0
- package/src/surreal/deserialize.ts +144 -0
- package/src/surreal/encryption.ts +51 -0
- package/src/ui/AnimatedChevron.tsx +32 -0
- package/src/ui/JsonInput.tsx +52 -0
- package/src/ui/RouterLink.tsx +78 -0
- package/src/ui/editor/Content.tsx +11 -0
- package/src/ui/editor/Provider.tsx +96 -0
- package/src/ui/editor/Root.tsx +25 -0
- package/src/ui/editor/Toolbar.tsx +92 -0
- package/src/ui/editor/index.ts +13 -0
- package/src/ui/editor/types.ts +7 -0
- package/src/ui/error/DefaultError.tsx +60 -0
- package/src/ui/error/DefaultNotFound.tsx +19 -0
- package/src/ui/error/Forbidden.tsx +18 -0
- package/src/ui/error/defaultErrorNotification.ts +9 -0
- package/src/ui/form/blurOnError.ts +21 -0
- package/src/ui/form/buttons/CancelButton.tsx +42 -0
- package/src/ui/form/buttons/SubmitButton.tsx +43 -0
- package/src/ui/form/buttons/SubscribeActionIcon.tsx +18 -0
- package/src/ui/form/buttons/SubscribeButton.tsx +17 -0
- package/src/ui/form/context.ts +45 -0
- package/src/ui/form/fields/JsonField.tsx +16 -0
- package/src/ui/form/fields/MultiSelectField.tsx +17 -0
- package/src/ui/form/fields/NumberField.tsx +17 -0
- package/src/ui/form/fields/PassowrdField.tsx +20 -0
- package/src/ui/form/fields/SelectField.tsx +17 -0
- package/src/ui/form/fields/SwitchField.tsx +17 -0
- package/src/ui/form/fields/TextField.tsx +17 -0
- package/src/ui/form/fields/TextPassowrdField.tsx +51 -0
- package/src/ui/form/fields/TextareaField.tsx +17 -0
- package/src/ui/form/fieldsSchema.ts +24 -0
- package/src/ui/hoverPaper/HoverPaper.tsx +17 -0
- package/src/ui/hoverPaper/usePaperHover.ts +9 -0
- package/src/ui/saveInput/JsonInput.tsx +40 -0
- package/src/ui/saveInput/NumberInput.tsx +40 -0
- package/src/ui/saveInput/SaveInput.tsx +15 -0
- package/src/ui/saveInput/Select.tsx +41 -0
- package/src/ui/saveInput/Switch.tsx +46 -0
- package/src/ui/saveInput/TextInput.tsx +40 -0
- package/src/ui/saveInput/Textarea.tsx +40 -0
- package/src/ui/scrollArea/ARCH.md +204 -0
- package/src/ui/scrollArea/README.md +369 -0
- package/{dist/ui/scrollArea/ScrollArea.d.ts → src/ui/scrollArea/ScrollArea.tsx} +41 -10
- package/src/ui/scrollArea/ScrollAreaButton.tsx +56 -0
- package/src/ui/scrollArea/ScrollAreaContent.tsx +36 -0
- package/{dist/ui/scrollArea/context.d.ts → src/ui/scrollArea/context.tsx} +18 -3
- package/src/ui/scrollArea/index.ts +10 -0
- package/src/ui/scrollArea/types.ts +77 -0
- package/src/ui/scrollArea/useScrollArea.ts +227 -0
- package/tsconfig.json +14 -0
- package/dist/ai/ui/conversation/ConversationContext.d.ts +0 -7
- package/dist/ai/ui/conversation/ConversationContext.js +0 -8
- package/dist/ai/ui/conversation/ConversationProvider.d.ts +0 -2
- package/dist/ai/ui/conversation/ConversationProvider.js +0 -14
- package/dist/ai/ui/conversation/Empty.d.ts +0 -1
- package/dist/ai/ui/conversation/Empty.js +0 -21
- package/dist/ai/ui/conversation/File.d.ts +0 -4
- package/dist/ai/ui/conversation/File.js +0 -42
- package/dist/ai/ui/conversation/FileIcon.d.ts +0 -3
- package/dist/ai/ui/conversation/FileIcon.js +0 -225
- package/dist/ai/ui/conversation/Loader.d.ts +0 -2
- package/dist/ai/ui/conversation/Loader.js +0 -12
- package/dist/ai/ui/conversation/Message.d.ts +0 -4
- package/dist/ai/ui/conversation/Message.js +0 -25
- package/dist/ai/ui/conversation/Root.d.ts +0 -2
- package/dist/ai/ui/conversation/Root.js +0 -26
- package/dist/ai/ui/conversation/index.d.ts +0 -13
- package/dist/ai/ui/conversation/index.js +0 -14
- package/dist/ai/ui/conversation/types.js +0 -0
- package/dist/ai/ui/conversation/useChatMessage.d.ts +0 -2
- package/dist/ai/ui/conversation/useChatMessage.js +0 -12
- package/dist/ai/ui/promptInput/File.d.ts +0 -2
- package/dist/ai/ui/promptInput/File.js +0 -117
- package/dist/ai/ui/promptInput/FileIcon.d.ts +0 -3
- package/dist/ai/ui/promptInput/FileIcon.js +0 -225
- package/dist/ai/ui/promptInput/Footer.d.ts +0 -2
- package/dist/ai/ui/promptInput/Footer.js +0 -8
- package/dist/ai/ui/promptInput/PromptInputContext.d.ts +0 -12
- package/dist/ai/ui/promptInput/PromptInputContext.js +0 -8
- package/dist/ai/ui/promptInput/PromptInputProvider.d.ts +0 -2
- package/dist/ai/ui/promptInput/PromptInputProvider.js +0 -50
- package/dist/ai/ui/promptInput/Root.d.ts +0 -3
- package/dist/ai/ui/promptInput/Root.js +0 -17
- package/dist/ai/ui/promptInput/Submit.d.ts +0 -2
- package/dist/ai/ui/promptInput/Submit.js +0 -40
- package/dist/ai/ui/promptInput/Textarea.d.ts +0 -2
- package/dist/ai/ui/promptInput/Textarea.js +0 -33
- package/dist/ai/ui/promptInput/index.d.ts +0 -8
- package/dist/ai/ui/promptInput/index.js +0 -13
- package/dist/ai/ui/promptInput/types.d.ts +0 -11
- package/dist/ai/ui/promptInput/types.js +0 -0
- package/dist/ai/utils/convertFileUIPartBlobToDataURL.d.ts +0 -5
- package/dist/ai/utils/convertFileUIPartBlobToDataURL.js +0 -21
- package/dist/ai/utils/parseAiMessagePart.d.ts +0 -2
- package/dist/ai/utils/parseAiMessagePart.js +0 -12
- package/dist/app/AppDefaults.d.ts +0 -3
- package/dist/app/AppDefaults.js +0 -27
- package/dist/app/DefaultApp.d.ts +0 -6
- package/dist/app/DefaultApp.js +0 -43
- package/dist/app/cookieColorSchemeManager.d.ts +0 -6
- package/dist/app/cookieColorSchemeManager.js +0 -46
- package/dist/app/defaultRequestMiddlewares.d.ts +0 -4
- package/dist/app/defaultRequestMiddlewares.js +0 -24
- package/dist/app/defaultTheme.d.ts +0 -141
- package/dist/app/defaultTheme.js +0 -24
- package/dist/functions/getCookie.d.ts +0 -3
- package/dist/functions/getCookie.js +0 -8
- package/dist/functions/setCookie.d.ts +0 -10
- package/dist/functions/setCookie.js +0 -19
- package/dist/functions/setCookies.d.ts +0 -14
- package/dist/functions/setCookies.js +0 -13
- package/dist/hooks/useMutation.d.ts +0 -4
- package/dist/hooks/useMutation.js +0 -8
- package/dist/hooks/useMutationWithInvalidate.d.ts +0 -4
- package/dist/hooks/useMutationWithInvalidate.js +0 -16
- package/dist/index.js +0 -26
- package/dist/surreal/connection.d.ts +0 -9
- package/dist/surreal/connection.js +0 -49
- package/dist/surreal/deafaultCrud.d.ts +0 -2
- package/dist/surreal/deafaultCrud.js +0 -18
- package/dist/surreal/deserialize.d.ts +0 -17
- package/dist/surreal/deserialize.js +0 -46
- package/dist/surreal/encryption.d.ts +0 -6
- package/dist/surreal/encryption.js +0 -30
- package/dist/ui/AnimatedChevron.d.ts +0 -6
- package/dist/ui/AnimatedChevron.js +0 -31
- package/dist/ui/JsonInput.d.ts +0 -2
- package/dist/ui/JsonInput.js +0 -45
- package/dist/ui/RouterLink.d.ts +0 -16
- package/dist/ui/RouterLink.js +0 -36
- package/dist/ui/editor/Content.d.ts +0 -3
- package/dist/ui/editor/Content.js +0 -13
- package/dist/ui/editor/Provider.d.ts +0 -17
- package/dist/ui/editor/Provider.js +0 -80
- package/dist/ui/editor/Root.d.ts +0 -2
- package/dist/ui/editor/Root.js +0 -18
- package/dist/ui/editor/Toolbar.d.ts +0 -5
- package/dist/ui/editor/Toolbar.js +0 -156
- package/dist/ui/editor/index.d.ts +0 -12
- package/dist/ui/editor/index.js +0 -11
- package/dist/ui/editor/types.d.ts +0 -7
- package/dist/ui/editor/types.js +0 -0
- package/dist/ui/error/DefaultError.d.ts +0 -2
- package/dist/ui/error/DefaultError.js +0 -62
- package/dist/ui/error/DefaultNotFound.d.ts +0 -1
- package/dist/ui/error/DefaultNotFound.js +0 -37
- package/dist/ui/error/Forbidden.d.ts +0 -1
- package/dist/ui/error/Forbidden.js +0 -32
- package/dist/ui/error/defaultErrorNotification.d.ts +0 -1
- package/dist/ui/error/defaultErrorNotification.js +0 -8
- package/dist/ui/error/index.js +0 -5
- package/dist/ui/form/blurOnError.d.ts +0 -4
- package/dist/ui/form/blurOnError.js +0 -11
- package/dist/ui/form/buttons/CancelButton.d.ts +0 -5
- package/dist/ui/form/buttons/CancelButton.js +0 -44
- package/dist/ui/form/buttons/SubmitButton.d.ts +0 -5
- package/dist/ui/form/buttons/SubmitButton.js +0 -47
- package/dist/ui/form/buttons/SubscribeActionIcon.d.ts +0 -4
- package/dist/ui/form/buttons/SubscribeActionIcon.js +0 -15
- package/dist/ui/form/buttons/SubscribeButton.d.ts +0 -5
- package/dist/ui/form/buttons/SubscribeButton.js +0 -16
- package/dist/ui/form/buttons/index.js +0 -4
- package/dist/ui/form/context.d.ts +0 -83
- package/dist/ui/form/context.js +0 -26
- package/dist/ui/form/fields/JsonField.d.ts +0 -2
- package/dist/ui/form/fields/JsonField.js +0 -13
- package/dist/ui/form/fields/MultiSelectField.d.ts +0 -2
- package/dist/ui/form/fields/MultiSelectField.js +0 -15
- package/dist/ui/form/fields/NumberField.d.ts +0 -2
- package/dist/ui/form/fields/NumberField.js +0 -15
- package/dist/ui/form/fields/PassowrdField.d.ts +0 -2
- package/dist/ui/form/fields/PassowrdField.js +0 -18
- package/dist/ui/form/fields/SelectField.d.ts +0 -2
- package/dist/ui/form/fields/SelectField.js +0 -15
- package/dist/ui/form/fields/SwitchField.d.ts +0 -2
- package/dist/ui/form/fields/SwitchField.js +0 -15
- package/dist/ui/form/fields/TextField.d.ts +0 -2
- package/dist/ui/form/fields/TextField.js +0 -15
- package/dist/ui/form/fields/TextPassowrdField.d.ts +0 -2
- package/dist/ui/form/fields/TextPassowrdField.js +0 -51
- package/dist/ui/form/fields/TextareaField.d.ts +0 -2
- package/dist/ui/form/fields/TextareaField.js +0 -15
- package/dist/ui/form/fields/index.js +0 -9
- package/dist/ui/form/fieldsSchema.d.ts +0 -12
- package/dist/ui/form/fieldsSchema.js +0 -13
- package/dist/ui/form/index.js +0 -4
- package/dist/ui/hoverPaper/HoverPaper.d.ts +0 -6
- package/dist/ui/hoverPaper/HoverPaper.js +0 -15
- package/dist/ui/hoverPaper/index.js +0 -3
- package/dist/ui/hoverPaper/usePaperHover.d.ts +0 -4
- package/dist/ui/hoverPaper/usePaperHover.js +0 -9
- package/dist/ui/saveInput/JsonInput.d.ts +0 -6
- package/dist/ui/saveInput/JsonInput.js +0 -34
- package/dist/ui/saveInput/NumberInput.d.ts +0 -6
- package/dist/ui/saveInput/NumberInput.js +0 -27
- package/dist/ui/saveInput/SaveInput.d.ts +0 -36
- package/dist/ui/saveInput/SaveInput.js +0 -15
- package/dist/ui/saveInput/Select.d.ts +0 -6
- package/dist/ui/saveInput/Select.js +0 -27
- package/dist/ui/saveInput/Switch.d.ts +0 -6
- package/dist/ui/saveInput/Switch.js +0 -30
- package/dist/ui/saveInput/TextInput.d.ts +0 -6
- package/dist/ui/saveInput/TextInput.js +0 -26
- package/dist/ui/saveInput/Textarea.d.ts +0 -6
- package/dist/ui/saveInput/Textarea.js +0 -26
- package/dist/ui/saveInput/index.js +0 -2
- package/dist/ui/scrollArea/ScrollArea.js +0 -30
- package/dist/ui/scrollArea/ScrollAreaButton.d.ts +0 -5
- package/dist/ui/scrollArea/ScrollAreaButton.js +0 -51
- package/dist/ui/scrollArea/ScrollAreaContent.d.ts +0 -6
- package/dist/ui/scrollArea/ScrollAreaContent.js +0 -29
- package/dist/ui/scrollArea/context.js +0 -10
- package/dist/ui/scrollArea/index.d.ts +0 -3
- package/dist/ui/scrollArea/index.js +0 -3
- package/dist/ui/scrollArea/types.d.ts +0 -65
- package/dist/ui/scrollArea/types.js +0 -0
- package/dist/ui/scrollArea/useScrollArea.d.ts +0 -9
- package/dist/ui/scrollArea/useScrollArea.js +0 -146
- /package/{dist/ui/error/index.d.ts → src/ui/error/index.ts} +0 -0
- /package/{dist/ui/form/buttons/index.d.ts → src/ui/form/buttons/index.ts} +0 -0
- /package/{dist/ui/form/fields/index.d.ts → src/ui/form/fields/index.ts} +0 -0
- /package/{dist/ui/form/index.d.ts → src/ui/form/index.ts} +0 -0
- /package/{dist/ui/hoverPaper/index.d.ts → src/ui/hoverPaper/index.ts} +0 -0
- /package/{dist/ui/saveInput/index.d.ts → src/ui/saveInput/index.ts} +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# ScrollArea Component
|
|
2
|
+
|
|
3
|
+
Расширенная область прокрутки с автоматическим скроллом и интеллектуальным управлением позиционированием на базе Mantine ScrollArea.
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
6
|
+
|
|
7
|
+
Компонент уже включен в kit компонентов:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ScrollArea, useScrollArea } from "@/components/kit";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Базовое использование
|
|
14
|
+
|
|
15
|
+
### Простая прокручиваемая область
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { ScrollArea } from "@/components/kit";
|
|
19
|
+
|
|
20
|
+
function BasicExample() {
|
|
21
|
+
return (
|
|
22
|
+
<ScrollArea h={300}>
|
|
23
|
+
<div>
|
|
24
|
+
{Array.from({ length: 50 }, (_, i) => (
|
|
25
|
+
<div key={i} style={{ padding: "10px" }}>
|
|
26
|
+
Элемент {i + 1}
|
|
27
|
+
</div>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
</ScrollArea>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Чат с автоскроллом
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { ScrollArea } from "@/components/kit";
|
|
39
|
+
|
|
40
|
+
function ChatMessages({ messages }) {
|
|
41
|
+
return (
|
|
42
|
+
<ScrollArea
|
|
43
|
+
autoScroll
|
|
44
|
+
scrollToBottomOnInit
|
|
45
|
+
animated
|
|
46
|
+
nearThreshold={100}
|
|
47
|
+
h={400}
|
|
48
|
+
>
|
|
49
|
+
{messages.map((msg) => (
|
|
50
|
+
<div
|
|
51
|
+
key={msg.id}
|
|
52
|
+
style={{ padding: "8px", borderBottom: "1px solid #eee" }}
|
|
53
|
+
>
|
|
54
|
+
<strong>{msg.author}:</strong> {msg.text}
|
|
55
|
+
</div>
|
|
56
|
+
))}
|
|
57
|
+
|
|
58
|
+
<ScrollArea.ScrollButton className="absolute bottom-4 right-4 z-10" />
|
|
59
|
+
</ScrollArea>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### Важно понимать:
|
|
67
|
+
|
|
68
|
+
- **ScrollArea** автоматически оборачивает `children` в `ScrollArea.Content`
|
|
69
|
+
- **ScrollArea.Content** нужен только когда требуется ref на прокручиваемую область
|
|
70
|
+
- **ScrollArea.ScrollButton** работает везде внутри ScrollArea через контекст
|
|
71
|
+
|
|
72
|
+
### ScrollArea Props
|
|
73
|
+
|
|
74
|
+
Наследует все пропсы от Mantine ScrollArea плюс дополнительные:
|
|
75
|
+
|
|
76
|
+
| Prop | Type | Default | Описание |
|
|
77
|
+
| ---------------------- | ------------------ | ------- | ----------------------------------------------- |
|
|
78
|
+
| `autoScroll` | `boolean` | `false` | Включение автоскролла при добавлении контента |
|
|
79
|
+
| `scrollToBottomOnInit` | `boolean` | `false` | Прокрутить к концу при инициализации компонента |
|
|
80
|
+
| `animated` | `boolean` | `true` | Анимированная прокрутка |
|
|
81
|
+
| `nearThreshold` | `number` | `100` | Отступ для near-зон в пикселях |
|
|
82
|
+
| `h` | `string \| number` | - | Высота области прокрутки |
|
|
83
|
+
| `scrollbarSize` | `number` | - | Размер полосы прокрутки |
|
|
84
|
+
| `type` | `string` | - | Тип поведения полос прокрутки |
|
|
85
|
+
| `...mantineProps` | - | - | Все остальные пропсы Mantine ScrollArea |
|
|
86
|
+
|
|
87
|
+
### Доступ к viewport элементу
|
|
88
|
+
|
|
89
|
+
Используйте `useScrollArea` для получения ref на прокручиваемую область:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
function MyComponent() {
|
|
93
|
+
const { viewportRef } = useScrollArea();
|
|
94
|
+
|
|
95
|
+
const scrollToElement = () => {
|
|
96
|
+
const target = viewportRef.current?.querySelector("[data-target]");
|
|
97
|
+
target?.scrollIntoView();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getScrollPosition = () => {
|
|
101
|
+
console.log("Scroll top:", viewportRef.current?.scrollTop);
|
|
102
|
+
console.log("Scroll height:", viewportRef.current?.scrollHeight);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<ScrollArea h={400}>
|
|
107
|
+
<div data-target>Элемент для прокрутки</div>
|
|
108
|
+
<button onClick={scrollToElement}>Прокрутить к элементу</button>
|
|
109
|
+
<button onClick={getScrollPosition}>Получить позицию</button>
|
|
110
|
+
</ScrollArea>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### ScrollArea.ScrollButton Props
|
|
116
|
+
|
|
117
|
+
✅ **Работает везде внутри ScrollArea** - использует контекст, не требует Content.
|
|
118
|
+
|
|
119
|
+
| Prop | Type | Default | Описание |
|
|
120
|
+
| ----------- | ----------- | --------------------- | -------------------------- |
|
|
121
|
+
| `className` | `string` | - | CSS классы |
|
|
122
|
+
| `upIcon` | `ReactNode` | `<IconChevronUp />` | Иконка для прокрутки вверх |
|
|
123
|
+
| `downIcon` | `ReactNode` | `<IconChevronDown />` | Иконка для прокрутки вниз |
|
|
124
|
+
|
|
125
|
+
#### Примеры размещения:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// В любом месте внутри ScrollArea
|
|
129
|
+
<ScrollArea h={400}>
|
|
130
|
+
{content}
|
|
131
|
+
<ScrollArea.ScrollButton className="absolute bottom-4 right-4" />
|
|
132
|
+
</ScrollArea>
|
|
133
|
+
|
|
134
|
+
// Даже вложенно
|
|
135
|
+
<ScrollArea h={400}>
|
|
136
|
+
<div className="relative">
|
|
137
|
+
{content}
|
|
138
|
+
<ScrollArea.ScrollButton />
|
|
139
|
+
</div>
|
|
140
|
+
</ScrollArea>
|
|
141
|
+
|
|
142
|
+
// Или в начале
|
|
143
|
+
<ScrollArea h={400}>
|
|
144
|
+
<ScrollArea.ScrollButton className="mb-4" />
|
|
145
|
+
{content}
|
|
146
|
+
</ScrollArea>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Контекст
|
|
150
|
+
|
|
151
|
+
### useScrollArea
|
|
152
|
+
|
|
153
|
+
Хук для доступа к состоянию и методам ScrollArea:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { ScrollArea, useScrollArea } from "@/components/kit";
|
|
157
|
+
|
|
158
|
+
function CustomControls() {
|
|
159
|
+
const {
|
|
160
|
+
isAtTop,
|
|
161
|
+
isNearTop,
|
|
162
|
+
isAtBottom,
|
|
163
|
+
isNearBottom,
|
|
164
|
+
isAboveCenter,
|
|
165
|
+
hasScrollableContent,
|
|
166
|
+
scrollToTop,
|
|
167
|
+
scrollToBottom,
|
|
168
|
+
viewportRef,
|
|
169
|
+
} = useScrollArea();
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div>
|
|
173
|
+
<button onClick={() => scrollToTop()} disabled={isAtTop}>
|
|
174
|
+
В начало
|
|
175
|
+
</button>
|
|
176
|
+
|
|
177
|
+
<button onClick={() => scrollToBottom()} disabled={isAtBottom}>
|
|
178
|
+
В конец
|
|
179
|
+
</button>
|
|
180
|
+
|
|
181
|
+
{hasScrollableContent && (
|
|
182
|
+
<span>{isAboveCenter ? "Верхняя половина" : "Нижняя половина"}</span>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function App() {
|
|
189
|
+
return (
|
|
190
|
+
<ScrollArea>
|
|
191
|
+
{/* контент */}
|
|
192
|
+
<CustomControls />
|
|
193
|
+
</ScrollArea>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Состояния позиции
|
|
199
|
+
|
|
200
|
+
- `isAtTop`: точно в начале (scrollTop === 0)
|
|
201
|
+
- `isNearTop`: в пределах nearThreshold от начала
|
|
202
|
+
- `isAtBottom`: точно в конце
|
|
203
|
+
- `isNearBottom`: в пределах nearThreshold от конца
|
|
204
|
+
- `isAboveCenter`: выше центральной точки
|
|
205
|
+
- `hasScrollableContent`: есть контент для прокрутки
|
|
206
|
+
|
|
207
|
+
### Методы управления
|
|
208
|
+
|
|
209
|
+
- `scrollToTop(animated?: boolean)`: прокрутить к началу
|
|
210
|
+
- `scrollToBottom(animated?: boolean)`: прокрутить к концу
|
|
211
|
+
- `viewportRef`: React RefObject на viewport элемент для прямого доступа
|
|
212
|
+
|
|
213
|
+
## Логика автоскролла
|
|
214
|
+
|
|
215
|
+
### Автоскролл при инициализации
|
|
216
|
+
|
|
217
|
+
Выполняется когда:
|
|
218
|
+
|
|
219
|
+
- `autoScroll === true`
|
|
220
|
+
- `scrollToBottomOnInit === true`
|
|
221
|
+
- Есть прокручиваемый контент
|
|
222
|
+
|
|
223
|
+
### Автоскролл при добавлении контента
|
|
224
|
+
|
|
225
|
+
Выполняется когда:
|
|
226
|
+
|
|
227
|
+
- `autoScroll === true`
|
|
228
|
+
- `isAtBottom === true` (точное позиционирование!)
|
|
229
|
+
- Нет активного взаимодействия пользователя
|
|
230
|
+
|
|
231
|
+
## Примеры использования
|
|
232
|
+
|
|
233
|
+
### Логи в реальном времени
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
function LogViewer({ logs }) {
|
|
237
|
+
return (
|
|
238
|
+
<ScrollArea
|
|
239
|
+
autoScroll
|
|
240
|
+
h={500}
|
|
241
|
+
style={{ fontFamily: "monospace", fontSize: "12px" }}
|
|
242
|
+
>
|
|
243
|
+
{logs.map((log, index) => (
|
|
244
|
+
<div key={index} style={{ padding: "2px 8px" }}>
|
|
245
|
+
<span style={{ color: "#666" }}>{log.timestamp}</span>
|
|
246
|
+
<span
|
|
247
|
+
style={{
|
|
248
|
+
color:
|
|
249
|
+
log.level === "error"
|
|
250
|
+
? "red"
|
|
251
|
+
: log.level === "warn"
|
|
252
|
+
? "orange"
|
|
253
|
+
: "inherit",
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{log.message}
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
))}
|
|
260
|
+
</ScrollArea>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Список с кастомными контролами
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
function ItemsList({ items }) {
|
|
269
|
+
return (
|
|
270
|
+
<ScrollArea nearThreshold={50} h={400}>
|
|
271
|
+
{items.map((item) => (
|
|
272
|
+
<div
|
|
273
|
+
key={item.id}
|
|
274
|
+
style={{ padding: "12px", borderBottom: "1px solid #eee" }}
|
|
275
|
+
>
|
|
276
|
+
{item.name}
|
|
277
|
+
</div>
|
|
278
|
+
))}
|
|
279
|
+
|
|
280
|
+
<div style={{ position: "absolute", top: "10px", right: "10px" }}>
|
|
281
|
+
<CustomScrollControls />
|
|
282
|
+
</div>
|
|
283
|
+
</ScrollArea>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function CustomScrollControls() {
|
|
288
|
+
const { scrollToTop, scrollToBottom, hasScrollableContent } = useScrollArea();
|
|
289
|
+
|
|
290
|
+
if (!hasScrollableContent) return null;
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div style={{ display: "flex", gap: "8px" }}>
|
|
294
|
+
<button onClick={() => scrollToTop()}>↑</button>
|
|
295
|
+
<button onClick={() => scrollToBottom()}>↓</button>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Комбинация с другими компонентами
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
import { ScrollArea } from "@/components/kit";
|
|
305
|
+
import { Button, Text, Badge } from "@mantine/core";
|
|
306
|
+
|
|
307
|
+
function NotificationCenter({ notifications }) {
|
|
308
|
+
return (
|
|
309
|
+
<ScrollArea autoScroll maxH={300} p="md">
|
|
310
|
+
{notifications.length === 0 ? (
|
|
311
|
+
<Text c="dimmed" ta="center" py="xl">
|
|
312
|
+
Нет уведомлений
|
|
313
|
+
</Text>
|
|
314
|
+
) : (
|
|
315
|
+
notifications.map((notification) => (
|
|
316
|
+
<div key={notification.id} style={{ marginBottom: "12px" }}>
|
|
317
|
+
<div
|
|
318
|
+
style={{
|
|
319
|
+
display: "flex",
|
|
320
|
+
justifyContent: "space-between",
|
|
321
|
+
alignItems: "center",
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
<Text size="sm" fw={500}>
|
|
325
|
+
{notification.title}
|
|
326
|
+
</Text>
|
|
327
|
+
<Badge
|
|
328
|
+
size="xs"
|
|
329
|
+
color={notification.priority === "high" ? "red" : "blue"}
|
|
330
|
+
>
|
|
331
|
+
{notification.priority}
|
|
332
|
+
</Badge>
|
|
333
|
+
</div>
|
|
334
|
+
<Text size="xs" c="dimmed" mt={4}>
|
|
335
|
+
{notification.message}
|
|
336
|
+
</Text>
|
|
337
|
+
</div>
|
|
338
|
+
))
|
|
339
|
+
)}
|
|
340
|
+
</ScrollArea>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Стилизация
|
|
346
|
+
|
|
347
|
+
Компонент полностью совместим с системой стилизации Mantine. Вы можете передавать все стандартные Mantine пропсы через `ScrollArea.Content`:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
<ScrollArea
|
|
351
|
+
h={400}
|
|
352
|
+
p="md"
|
|
353
|
+
style={{ backgroundColor: "#f8f9fa" }}
|
|
354
|
+
scrollbarSize={6}
|
|
355
|
+
scrollHideDelay={1500}
|
|
356
|
+
type="hover"
|
|
357
|
+
>
|
|
358
|
+
{/* контент */}
|
|
359
|
+
</ScrollArea>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Производительность
|
|
363
|
+
|
|
364
|
+
Компонент оптимизирован для производительности:
|
|
365
|
+
|
|
366
|
+
- Дебаунсинг событий скролла (16ms для 60fps)
|
|
367
|
+
- Оптимальное отслеживание изменений размера через ResizeObserver
|
|
368
|
+
- Минимальные ререндеры благодаря правильной мемоизации
|
|
369
|
+
- Автоматическая очистка event listeners и таймеров
|
|
@@ -1,4 +1,41 @@
|
|
|
1
|
+
import { ScrollAreaProvider } from './context';
|
|
2
|
+
import { ScrollAreaButton } from './ScrollAreaButton';
|
|
3
|
+
import { ScrollAreaContent } from './ScrollAreaContent';
|
|
1
4
|
import type { ScrollAreaContextValue, ScrollAreaProps } from './types';
|
|
5
|
+
import { useScrollAreaState } from './useScrollArea';
|
|
6
|
+
|
|
7
|
+
const ScrollAreaRoot = ({
|
|
8
|
+
children,
|
|
9
|
+
autoScroll = false,
|
|
10
|
+
scrollToBottomOnInit = false,
|
|
11
|
+
animated = true,
|
|
12
|
+
nearThreshold = 100,
|
|
13
|
+
radius,
|
|
14
|
+
...mantineProps
|
|
15
|
+
}: ScrollAreaProps) => {
|
|
16
|
+
const scrollAreaValue = useScrollAreaState({
|
|
17
|
+
autoScroll,
|
|
18
|
+
scrollToBottomOnInit,
|
|
19
|
+
animated,
|
|
20
|
+
nearThreshold,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Создаем контекст с mantineProps
|
|
24
|
+
const contextValue: ScrollAreaContextValue = {
|
|
25
|
+
...scrollAreaValue,
|
|
26
|
+
radius,
|
|
27
|
+
mantineProps,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ScrollAreaProvider value={contextValue}>
|
|
32
|
+
<ScrollAreaContent>{children}</ScrollAreaContent>
|
|
33
|
+
</ScrollAreaProvider>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
ScrollAreaRoot.displayName = 'ScrollArea';
|
|
38
|
+
|
|
2
39
|
/**
|
|
3
40
|
* ScrollArea Component - Расширенная область прокрутки с автоскроллом
|
|
4
41
|
*
|
|
@@ -50,13 +87,7 @@ import type { ScrollAreaContextValue, ScrollAreaProps } from './types';
|
|
|
50
87
|
* );
|
|
51
88
|
* }
|
|
52
89
|
*/
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
ScrollButton: {
|
|
58
|
-
({ className }: import("./types").ScrollButtonProps): import("react/jsx-runtime").JSX.Element | null;
|
|
59
|
-
displayName: string;
|
|
60
|
-
};
|
|
61
|
-
Provider: import("react").Provider<ScrollAreaContextValue | null>;
|
|
62
|
-
};
|
|
90
|
+
export const ScrollArea = Object.assign(ScrollAreaRoot, {
|
|
91
|
+
ScrollButton: ScrollAreaButton,
|
|
92
|
+
Provider: ScrollAreaProvider,
|
|
93
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ActionIcon } from '@mantine/core';
|
|
2
|
+
import { useScrollArea } from './context';
|
|
3
|
+
import type { ScrollButtonProps } from './types';
|
|
4
|
+
|
|
5
|
+
export const ScrollAreaButton = ({ className }: ScrollButtonProps) => {
|
|
6
|
+
const { hasScrollableContent, isAboveCenter, scrollToTop, scrollToBottom } =
|
|
7
|
+
useScrollArea();
|
|
8
|
+
|
|
9
|
+
// Не показываем кнопку, если нет прокручиваемого контента
|
|
10
|
+
if (!hasScrollableContent) return null;
|
|
11
|
+
|
|
12
|
+
const isScrollingDown = isAboveCenter;
|
|
13
|
+
const icon = (
|
|
14
|
+
<svg
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
width="24"
|
|
17
|
+
height="24"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
strokeWidth="1.5"
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
role="img"
|
|
25
|
+
aria-label="Chevron"
|
|
26
|
+
style={{
|
|
27
|
+
transition: 'transform 0.2s ease-in-out',
|
|
28
|
+
transform: isScrollingDown ? 'rotate(-180deg)' : 'rotate(0deg)',
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
32
|
+
<path d="M6 9l6 6l6 -6" />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const handleClick = () => {
|
|
37
|
+
if (isScrollingDown) scrollToBottom();
|
|
38
|
+
else scrollToTop();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ActionIcon
|
|
43
|
+
pos="absolute"
|
|
44
|
+
bottom={16}
|
|
45
|
+
right={16}
|
|
46
|
+
variant="light"
|
|
47
|
+
onClick={handleClick}
|
|
48
|
+
className={className}
|
|
49
|
+
aria-label={isScrollingDown ? 'Scroll to bottom' : 'Scroll to top'}
|
|
50
|
+
>
|
|
51
|
+
{icon}
|
|
52
|
+
</ActionIcon>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
ScrollAreaButton.displayName = 'ScrollArea.ScrollButton';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Box, getRadius, ScrollArea as MantineScrollArea } from '@mantine/core';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { ScrollAreaContext } from './context';
|
|
4
|
+
|
|
5
|
+
export const ScrollAreaContent = ({
|
|
6
|
+
children,
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}) => {
|
|
10
|
+
// Получаем полное состояние из контекста (включая внутренние детали)
|
|
11
|
+
const fullContext = useContext(ScrollAreaContext);
|
|
12
|
+
if (!fullContext) {
|
|
13
|
+
throw new Error('ScrollAreaContent must be used within ScrollArea');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { _callbackRef, mantineProps, radius, _contentResizeRef } = fullContext;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<MantineScrollArea
|
|
20
|
+
classNames={{
|
|
21
|
+
viewport: 'rolder-scroll-area-viewport',
|
|
22
|
+
content: 'rolder-scroll-area-content',
|
|
23
|
+
scrollbar: 'rolder-scroll-area-scrollbar',
|
|
24
|
+
}}
|
|
25
|
+
style={{ '--radius': radius ? getRadius(radius) : undefined }}
|
|
26
|
+
{...mantineProps}
|
|
27
|
+
viewportRef={_callbackRef}
|
|
28
|
+
>
|
|
29
|
+
<Box h="100%" w="100%" ref={_contentResizeRef}>
|
|
30
|
+
{children}
|
|
31
|
+
</Box>
|
|
32
|
+
</MantineScrollArea>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
ScrollAreaContent.displayName = 'ScrollArea.Content';
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
1
2
|
import type { ScrollAreaContextValue, ScrollAreaHook } from './types';
|
|
3
|
+
|
|
2
4
|
/** Внутренний контекст для передачи состояния ScrollArea */
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
+
export const ScrollAreaContext = createContext<ScrollAreaContextValue | null>(
|
|
6
|
+
null,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export const ScrollAreaProvider = ScrollAreaContext.Provider;
|
|
10
|
+
|
|
5
11
|
/**
|
|
6
12
|
* Хук для доступа к состоянию и методам ScrollArea
|
|
7
13
|
*
|
|
@@ -25,4 +31,13 @@ export declare const ScrollAreaProvider: import("react").Provider<ScrollAreaCont
|
|
|
25
31
|
* }
|
|
26
32
|
* ```
|
|
27
33
|
*/
|
|
28
|
-
export
|
|
34
|
+
export const useScrollArea = (): ScrollAreaHook => {
|
|
35
|
+
const context = useContext(ScrollAreaContext);
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error('useScrollArea must be used within ScrollArea');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Возвращаем только публичный API, скрывая внутренние детали
|
|
41
|
+
const { _callbackRef, mantineProps: _, radius: __, ...publicAPI } = context;
|
|
42
|
+
return publicAPI;
|
|
43
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MantineRadius,
|
|
3
|
+
ScrollAreaProps as MantineScrollAreaProps,
|
|
4
|
+
} from '@mantine/core';
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
export interface ScrollAreaProps
|
|
8
|
+
extends Omit<MantineScrollAreaProps, 'children'> {
|
|
9
|
+
/** Включение автоскролла при добавлении контента (по умолчанию false) */
|
|
10
|
+
autoScroll?: boolean;
|
|
11
|
+
/** Прокрутить к концу при инициализации компонента (по умолчанию false) */
|
|
12
|
+
scrollToBottomOnInit?: boolean;
|
|
13
|
+
/** Анимированная прокрутка (по умолчанию true) */
|
|
14
|
+
animated?: boolean;
|
|
15
|
+
/** Отступ для near-зон в пикселях (по умолчанию 100) */
|
|
16
|
+
nearThreshold?: number;
|
|
17
|
+
/** Радиус для viewport и scrollbar на тот случай, когда они "торчат" углами*/
|
|
18
|
+
radius?: MantineRadius;
|
|
19
|
+
/** Дочерние элементы - автоматически оборачиваются в ScrollArea.Content */
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ScrollAreaHook {
|
|
24
|
+
/** Точно в начале (scrollTop === 0) */
|
|
25
|
+
isAtTop: boolean;
|
|
26
|
+
/** В пределах nearThreshold от начала */
|
|
27
|
+
isNearTop: boolean;
|
|
28
|
+
/** Точно в конце (scrollTop + clientHeight >= scrollHeight) */
|
|
29
|
+
isAtBottom: boolean;
|
|
30
|
+
/** В пределах nearThreshold от конца */
|
|
31
|
+
isNearBottom: boolean;
|
|
32
|
+
/** Выше центральной точки области прокрутки */
|
|
33
|
+
isAboveCenter: boolean;
|
|
34
|
+
/** Есть контент требующий прокрутки */
|
|
35
|
+
hasScrollableContent: boolean;
|
|
36
|
+
|
|
37
|
+
/** Прокрутить к началу области */
|
|
38
|
+
scrollToTop: (animated?: boolean) => void;
|
|
39
|
+
/** Прокрутить к концу области */
|
|
40
|
+
scrollToBottom: (animated?: boolean) => void;
|
|
41
|
+
|
|
42
|
+
/** React ref на viewport элемент для прямого доступа к DOM */
|
|
43
|
+
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ScrollAreaState extends ScrollAreaHook {
|
|
47
|
+
/** Внутренний callback ref для подключения к Mantine ScrollArea */
|
|
48
|
+
_callbackRef: (node: HTMLDivElement | null) => void;
|
|
49
|
+
/** Внутренний ref для наблюдения за изменениями контента */
|
|
50
|
+
_contentResizeRef: (node: HTMLDivElement | null) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ScrollAreaContextValue extends ScrollAreaState {
|
|
54
|
+
/** Радиус для viewport и scrollbar на тот случай, когда они "торчат" углами*/
|
|
55
|
+
radius?: MantineRadius;
|
|
56
|
+
/** Пропсы Mantine ScrollArea, переданные в корневой компонент */
|
|
57
|
+
mantineProps: Omit<MantineScrollAreaProps, 'children'>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ScrollButtonProps {
|
|
61
|
+
/** CSS классы для стилизации кнопки */
|
|
62
|
+
className?: string;
|
|
63
|
+
/** Иконка для прокрутки вверх (по умолчанию IconChevronUp) */
|
|
64
|
+
upIcon?: ReactNode;
|
|
65
|
+
/** Иконка для прокрутки вниз (по умолчанию IconChevronDown) */
|
|
66
|
+
downIcon?: ReactNode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Внутренний тип для отслеживания позиции скролла */
|
|
70
|
+
export interface ScrollPosition {
|
|
71
|
+
/** Текущая позиция скролла сверху */
|
|
72
|
+
scrollTop: number;
|
|
73
|
+
/** Высота видимой области */
|
|
74
|
+
clientHeight: number;
|
|
75
|
+
/** Общая высота прокручиваемого контента */
|
|
76
|
+
scrollHeight: number;
|
|
77
|
+
}
|