@coyalabs/bts-style 1.3.14 → 1.3.17
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/README.md +58 -8
- package/dist/Base/BaseText.svelte +6 -5
- package/dist/Components/ContextMenu.svelte +175 -29
- package/dist/Components/InputBox.svelte +241 -18
- package/dist/Components/InputBox.svelte.d.ts +23 -1
- package/dist/Components/Popup/AlertPopup.svelte +8 -0
- package/dist/Components/Popup/AlertPopup.svelte.d.ts +11 -1
- package/dist/Components/Popup/ConfirmPopup.svelte +8 -0
- package/dist/Components/Popup/ConfirmPopup.svelte.d.ts +11 -1
- package/dist/Components/Popup/Popup.svelte +90 -3
- package/dist/Components/Popup/PromptPopup.svelte +16 -1
- package/dist/Components/Popup/PromptPopup.svelte.d.ts +15 -1
- package/dist/Components/Popup/popupStore.d.ts +12 -0
- package/dist/Components/Popup/popupStore.js +57 -29
- package/dist/Components/ScrollContainer.svelte +88 -3
- package/dist/Components/ScrollContainer.svelte.d.ts +2 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -340,6 +340,11 @@ Text input field with icon support and theme matching.
|
|
|
340
340
|
- `value?: string` - Input value (bindable)
|
|
341
341
|
- `placeholder?: string` - Placeholder text
|
|
342
342
|
- `type?: string` - Input type (default: `'text'`)
|
|
343
|
+
- `multiline?: boolean` - Render a textarea instead of an input (default: `false`)
|
|
344
|
+
- `maxRows?: number` - Maximum visible rows for auto-growing multiline inputs (default: `4`)
|
|
345
|
+
- `expandOnNewline?: boolean` - Keep a multiline input visually single-line until the value contains a newline (default: `false`)
|
|
346
|
+
- `newlineOnCtrlEnter?: boolean` - For multiline inputs, plain Enter submits the nearest form or bubbles to parent handlers, while `Ctrl+Enter` and `Shift+Enter` insert a newline (default: `false`)
|
|
347
|
+
- `resize?: 'none' | 'both' | 'horizontal' | 'vertical'` - Textarea resize mode when `multiline` is enabled (default: `'vertical'`)
|
|
343
348
|
- `theme?: 'full' | 'primary' | 'secondary' | 'filled'` - Visual theme
|
|
344
349
|
- `icon?: string` - Left icon SVG
|
|
345
350
|
- All BaseContainer corner radius props
|
|
@@ -357,6 +362,33 @@ Text input field with icon support and theme matching.
|
|
|
357
362
|
icon={icons.pen}
|
|
358
363
|
theme="primary"
|
|
359
364
|
/>
|
|
365
|
+
|
|
366
|
+
<InputBox
|
|
367
|
+
bind:value={username}
|
|
368
|
+
placeholder="Write a longer note..."
|
|
369
|
+
icon={icons.pen}
|
|
370
|
+
multiline={true}
|
|
371
|
+
maxRows={6}
|
|
372
|
+
resize="vertical"
|
|
373
|
+
/>
|
|
374
|
+
|
|
375
|
+
<InputBox
|
|
376
|
+
bind:value={username}
|
|
377
|
+
placeholder="Looks like one line until you press Enter..."
|
|
378
|
+
icon={icons.pen}
|
|
379
|
+
multiline={true}
|
|
380
|
+
expandOnNewline={true}
|
|
381
|
+
maxRows={4}
|
|
382
|
+
/>
|
|
383
|
+
|
|
384
|
+
<InputBox
|
|
385
|
+
bind:value={username}
|
|
386
|
+
placeholder="Press Enter to submit, Ctrl+Enter for a newline"
|
|
387
|
+
icon={icons.pen}
|
|
388
|
+
multiline={true}
|
|
389
|
+
newlineOnCtrlEnter={true}
|
|
390
|
+
maxRows={4}
|
|
391
|
+
/>
|
|
360
392
|
```
|
|
361
393
|
|
|
362
394
|
---
|
|
@@ -429,6 +461,7 @@ Reusable overflow wrapper with theme-matching custom scrollbar styling.
|
|
|
429
461
|
- `thumbColor?: string` - Scrollbar thumb color (default: `'rgba(161, 143, 143, 0.3)'`)
|
|
430
462
|
- `thumbHoverColor?: string` - Scrollbar thumb hover color (default: `'rgba(161, 143, 143, 0.5)'`)
|
|
431
463
|
- `borderRadius?: string` - Scrollbar track/thumb radius (default: `'4px'`)
|
|
464
|
+
- `hideScrollbarUntilScroll?: boolean` - Hide scrollbars until the container has been scrolled from its initial position (default: `false`)
|
|
432
465
|
|
|
433
466
|
**Example:**
|
|
434
467
|
```svelte
|
|
@@ -441,6 +474,12 @@ Reusable overflow wrapper with theme-matching custom scrollbar styling.
|
|
|
441
474
|
Scrollable content with BTS-themed scrollbar.
|
|
442
475
|
</div>
|
|
443
476
|
</ScrollContainer>
|
|
477
|
+
|
|
478
|
+
<ScrollContainer overflowY="auto" maxHeight="320px" hideScrollbarUntilScroll={true}>
|
|
479
|
+
<div style="min-height: 640px;">
|
|
480
|
+
Scrollbars stay hidden until the first real scroll.
|
|
481
|
+
</div>
|
|
482
|
+
</ScrollContainer>
|
|
444
483
|
```
|
|
445
484
|
|
|
446
485
|
**Features:**
|
|
@@ -779,13 +818,18 @@ Writable store for controlling the popup.
|
|
|
779
818
|
popupStore.open(
|
|
780
819
|
title: string,
|
|
781
820
|
component: SvelteComponent,
|
|
782
|
-
props?: object
|
|
821
|
+
props?: object & {
|
|
822
|
+
popupWidthRatio?: number
|
|
823
|
+
},
|
|
783
824
|
subtitle?: string
|
|
784
825
|
)
|
|
785
826
|
```
|
|
786
827
|
|
|
787
828
|
Opens popup with custom component.
|
|
788
829
|
|
|
830
|
+
`popupWidthRatio` multiplies the default popup width while keeping the dialog capped to the viewport.
|
|
831
|
+
Use `1` for the current width, values below `1` for narrower dialogs, and values above `1` for wider dialogs.
|
|
832
|
+
|
|
789
833
|
**Example:**
|
|
790
834
|
```svelte
|
|
791
835
|
<script>
|
|
@@ -797,7 +841,7 @@ Opens popup with custom component.
|
|
|
797
841
|
popupStore.open(
|
|
798
842
|
'Settings',
|
|
799
843
|
MyCustomPopup,
|
|
800
|
-
{ userId: 123 },
|
|
844
|
+
{ userId: 123, popupWidthRatio: 1.25 },
|
|
801
845
|
'Configure your preferences'
|
|
802
846
|
)
|
|
803
847
|
}>
|
|
@@ -814,7 +858,8 @@ popupStore.confirm(
|
|
|
814
858
|
onConfirm?: () => void,
|
|
815
859
|
onCancel?: () => void,
|
|
816
860
|
confirmText?: string,
|
|
817
|
-
cancelText?: string
|
|
861
|
+
cancelText?: string,
|
|
862
|
+
popupWidthRatio?: number
|
|
818
863
|
}
|
|
819
864
|
)
|
|
820
865
|
```
|
|
@@ -831,7 +876,8 @@ Shows confirmation dialog.
|
|
|
831
876
|
onConfirm: () => deleteItem(),
|
|
832
877
|
onCancel: () => console.log('Cancelled'),
|
|
833
878
|
confirmText: 'Delete',
|
|
834
|
-
cancelText: 'Keep'
|
|
879
|
+
cancelText: 'Keep',
|
|
880
|
+
popupWidthRatio: 1.15
|
|
835
881
|
}
|
|
836
882
|
)
|
|
837
883
|
}>
|
|
@@ -846,7 +892,8 @@ popupStore.alert(
|
|
|
846
892
|
message: string,
|
|
847
893
|
options?: {
|
|
848
894
|
onOk?: () => void,
|
|
849
|
-
okText?: string
|
|
895
|
+
okText?: string,
|
|
896
|
+
popupWidthRatio?: number
|
|
850
897
|
}
|
|
851
898
|
)
|
|
852
899
|
```
|
|
@@ -860,7 +907,8 @@ popupStore.alert(
|
|
|
860
907
|
'Your changes have been saved!',
|
|
861
908
|
{
|
|
862
909
|
onOk: () => navigateToHome(),
|
|
863
|
-
okText: 'Got it'
|
|
910
|
+
okText: 'Got it',
|
|
911
|
+
popupWidthRatio: 0.9
|
|
864
912
|
}
|
|
865
913
|
);
|
|
866
914
|
```
|
|
@@ -875,7 +923,8 @@ popupStore.prompt(
|
|
|
875
923
|
onCancel?: () => void,
|
|
876
924
|
placeholder?: string,
|
|
877
925
|
submitText?: string,
|
|
878
|
-
cancelText?: string
|
|
926
|
+
cancelText?: string,
|
|
927
|
+
popupWidthRatio?: number
|
|
879
928
|
}
|
|
880
929
|
)
|
|
881
930
|
```
|
|
@@ -891,7 +940,8 @@ popupStore.prompt(
|
|
|
891
940
|
onSubmit: (name) => updateProfile(name),
|
|
892
941
|
placeholder: 'Your name...',
|
|
893
942
|
submitText: 'Save',
|
|
894
|
-
cancelText: 'Skip'
|
|
943
|
+
cancelText: 'Skip',
|
|
944
|
+
popupWidthRatio: 0.95
|
|
895
945
|
}
|
|
896
946
|
);
|
|
897
947
|
```
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
|
|
36
36
|
$: usesContent = content !== null && content !== undefined;
|
|
37
37
|
$: tagName = as || (markdown && usesContent ? 'div' : 'span');
|
|
38
|
-
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
$: renderedContent = markdown && usesContent ? renderMarkdown(content) : '';
|
|
39
40
|
</script>
|
|
40
41
|
|
|
41
42
|
<svelte:element
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
color: #C7BDC1;
|
|
123
124
|
}
|
|
124
125
|
.text[data-markdown='true'] :global(a) {
|
|
125
|
-
color: #
|
|
126
|
+
color: #67abff;
|
|
126
127
|
text-decoration: underline;
|
|
127
128
|
text-decoration-thickness: 1px;
|
|
128
129
|
text-underline-offset: 0.14em;
|
|
@@ -201,17 +202,17 @@
|
|
|
201
202
|
}
|
|
202
203
|
.text[data-variant='content'][data-markdown='true'] :global(h1) {
|
|
203
204
|
font-size: calc(24px + var(--text-modifier));
|
|
204
|
-
font-weight: calc(
|
|
205
|
+
font-weight: calc(900 + var(--text-weight-modifier));
|
|
205
206
|
}
|
|
206
207
|
.text[data-variant='content'][data-markdown='true'] :global(h2) {
|
|
207
208
|
font-size: calc(21px + var(--text-modifier));
|
|
208
|
-
font-weight: calc(
|
|
209
|
+
font-weight: calc(900 + var(--text-weight-modifier));
|
|
209
210
|
}
|
|
210
211
|
.text[data-variant='content'][data-markdown='true'] :global(h3),
|
|
211
212
|
.text[data-variant='button'][data-markdown='true'] :global(h1),
|
|
212
213
|
.text[data-variant='button'][data-markdown='true'] :global(h2),
|
|
213
214
|
.text[data-variant='button'][data-markdown='true'] :global(h3) {
|
|
214
215
|
font-size: calc(18px + var(--text-modifier));
|
|
215
|
-
font-weight: calc(
|
|
216
|
+
font-weight: calc(900 + var(--text-weight-modifier));
|
|
216
217
|
}
|
|
217
218
|
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { onMount, tick } from 'svelte';
|
|
2
3
|
import { expoOut } from 'svelte/easing';
|
|
3
4
|
import BaseContainer from '../Base/BaseContainer.svelte';
|
|
4
5
|
import BaseText from '../Base/BaseText.svelte';
|
|
@@ -33,6 +34,68 @@
|
|
|
33
34
|
'bottom-right': 'bottom right'
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
const VIEWPORT_PADDING = 12;
|
|
38
|
+
|
|
39
|
+
/** @type {HTMLDivElement | null} */
|
|
40
|
+
let anchorRef = null;
|
|
41
|
+
|
|
42
|
+
let viewportOffsetX = 0;
|
|
43
|
+
let viewportOffsetY = 0;
|
|
44
|
+
|
|
45
|
+
/** @type {number | null} */
|
|
46
|
+
let updateFrame = null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {number} position
|
|
50
|
+
* @param {number} size
|
|
51
|
+
* @param {number} viewportSize
|
|
52
|
+
*/
|
|
53
|
+
function getViewportOffset(position, size, viewportSize) {
|
|
54
|
+
const viewportMin = VIEWPORT_PADDING;
|
|
55
|
+
const viewportMax = viewportSize - VIEWPORT_PADDING;
|
|
56
|
+
const availableSize = Math.max(viewportSize - VIEWPORT_PADDING * 2, 0);
|
|
57
|
+
|
|
58
|
+
if (size >= availableSize) {
|
|
59
|
+
return viewportMin - position;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (position < viewportMin) {
|
|
63
|
+
return viewportMin - position;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const end = position + size;
|
|
67
|
+
if (end > viewportMax) {
|
|
68
|
+
return viewportMax - end;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function updateViewportOffset() {
|
|
75
|
+
if (!anchorRef || typeof window === 'undefined') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rect = anchorRef.getBoundingClientRect();
|
|
80
|
+
viewportOffsetX = getViewportOffset(rect.left, rect.width, window.innerWidth);
|
|
81
|
+
viewportOffsetY = getViewportOffset(rect.top, rect.height, window.innerHeight);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scheduleViewportOffsetUpdate() {
|
|
85
|
+
if (typeof window === 'undefined') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (updateFrame !== null) {
|
|
90
|
+
cancelAnimationFrame(updateFrame);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
updateFrame = requestAnimationFrame(() => {
|
|
94
|
+
updateFrame = null;
|
|
95
|
+
updateViewportOffset();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
36
99
|
/**
|
|
37
100
|
* Custom scale transition with independent x and y animations
|
|
38
101
|
* @param {HTMLElement} node
|
|
@@ -91,45 +154,128 @@
|
|
|
91
154
|
function handleSelect(value) {
|
|
92
155
|
onSelect(value);
|
|
93
156
|
}
|
|
157
|
+
|
|
158
|
+
onMount(() => {
|
|
159
|
+
scheduleViewportOffsetUpdate();
|
|
160
|
+
void tick().then(() => {
|
|
161
|
+
scheduleViewportOffsetUpdate();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** @type {ResizeObserver | null} */
|
|
165
|
+
let resizeObserver = null;
|
|
166
|
+
|
|
167
|
+
if (typeof ResizeObserver !== 'undefined' && anchorRef) {
|
|
168
|
+
resizeObserver = new ResizeObserver(() => {
|
|
169
|
+
scheduleViewportOffsetUpdate();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
resizeObserver.observe(anchorRef);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
window.addEventListener('resize', scheduleViewportOffsetUpdate);
|
|
176
|
+
window.addEventListener('scroll', scheduleViewportOffsetUpdate, true);
|
|
177
|
+
|
|
178
|
+
return () => {
|
|
179
|
+
resizeObserver?.disconnect();
|
|
180
|
+
window.removeEventListener('resize', scheduleViewportOffsetUpdate);
|
|
181
|
+
window.removeEventListener('scroll', scheduleViewportOffsetUpdate, true);
|
|
182
|
+
|
|
183
|
+
if (updateFrame !== null) {
|
|
184
|
+
cancelAnimationFrame(updateFrame);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
});
|
|
94
188
|
</script>
|
|
95
189
|
|
|
96
|
-
<div
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
190
|
+
<div class="context-menu-anchor" bind:this={anchorRef}>
|
|
191
|
+
<div
|
|
192
|
+
class="context-menu-clamp"
|
|
193
|
+
style="
|
|
194
|
+
--context-menu-offset-x: {viewportOffsetX}px;
|
|
195
|
+
--context-menu-offset-y: {viewportOffsetY}px;
|
|
196
|
+
--context-menu-max-width: calc(100vw - {VIEWPORT_PADDING * 2}px);
|
|
197
|
+
--context-menu-max-height: calc(100vh - {VIEWPORT_PADDING * 2}px);
|
|
198
|
+
"
|
|
199
|
+
>
|
|
200
|
+
<div
|
|
201
|
+
class="context-menu-wrapper"
|
|
202
|
+
style:transform-origin={originMap[origin]}
|
|
203
|
+
in:scaleIn
|
|
204
|
+
out:scaleOut
|
|
205
|
+
>
|
|
206
|
+
<div class="context-menu-surface">
|
|
207
|
+
<BaseContainer theme="filled" padding="0.5rem" borderRadiusTopLeft="28px" borderRadiusTopRight="28px" borderRadiusBottomLeft="28px" borderRadiusBottomRight="28px">
|
|
208
|
+
<div style="height: 0.3rem;"></div>
|
|
209
|
+
{#each categories as category, catIndex}
|
|
210
|
+
{#if category.label}
|
|
211
|
+
<div class="separator" class:not-first={catIndex > 0}>
|
|
212
|
+
<BaseText textModifier="-4px" variant="button">{category.label}</BaseText>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
<div class="category-items">
|
|
216
|
+
{#each category.items as {item}}
|
|
217
|
+
<button
|
|
218
|
+
class="context-item"
|
|
219
|
+
class:selected={item.value === selectedValue}
|
|
220
|
+
class:disabled={item.disabled}
|
|
221
|
+
disabled={item.disabled}
|
|
222
|
+
on:click={() => handleSelect(item.value)}
|
|
223
|
+
>
|
|
224
|
+
{item.label}
|
|
225
|
+
</button>
|
|
226
|
+
{/each}
|
|
227
|
+
</div>
|
|
228
|
+
{/each}
|
|
229
|
+
<div style="height: 0.3rem;"></div>
|
|
230
|
+
</BaseContainer>
|
|
122
231
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
</BaseContainer>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
126
234
|
</div>
|
|
127
235
|
|
|
128
236
|
<style>
|
|
237
|
+
.context-menu-anchor {
|
|
238
|
+
display: inline-block;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.context-menu-clamp {
|
|
242
|
+
display: inline-block;
|
|
243
|
+
transform: translate3d(var(--context-menu-offset-x), var(--context-menu-offset-y), 0);
|
|
244
|
+
}
|
|
245
|
+
|
|
129
246
|
.context-menu-wrapper {
|
|
130
247
|
display: inline-block;
|
|
131
248
|
}
|
|
132
249
|
|
|
250
|
+
.context-menu-surface {
|
|
251
|
+
display: inline-block;
|
|
252
|
+
max-width: var(--context-menu-max-width);
|
|
253
|
+
max-height: var(--context-menu-max-height);
|
|
254
|
+
overflow-x: hidden;
|
|
255
|
+
overflow-y: auto;
|
|
256
|
+
overscroll-behavior: contain;
|
|
257
|
+
border-radius: 28px;
|
|
258
|
+
scrollbar-width: thin;
|
|
259
|
+
scrollbar-color: rgba(161, 143, 143, 0.45) rgba(62, 53, 58, 0.35);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.context-menu-surface::-webkit-scrollbar {
|
|
263
|
+
width: 8px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.context-menu-surface::-webkit-scrollbar-track {
|
|
267
|
+
background: rgba(62, 53, 58, 0.35);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.context-menu-surface::-webkit-scrollbar-thumb {
|
|
271
|
+
background: rgba(161, 143, 143, 0.45);
|
|
272
|
+
border-radius: 999px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.context-menu-surface::-webkit-scrollbar-thumb:hover {
|
|
276
|
+
background: rgba(161, 143, 143, 0.65);
|
|
277
|
+
}
|
|
278
|
+
|
|
133
279
|
.category-items {
|
|
134
280
|
display: flex;
|
|
135
281
|
flex-direction: column;
|