@sveltia/ui 0.76.1 → 0.77.1
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/dist/components/drawer/drawer.d.ts +81 -0
- package/dist/components/drawer/drawer.js +110 -0
- package/dist/components/drawer/drawer.svelte +183 -1
- package/dist/components/drawer/drawer.svelte.d.ts +16 -0
- package/dist/components/text-editor/shiki/version.d.ts +1 -1
- package/dist/components/text-editor/shiki/version.js +1 -1
- package/dist/components/util/fonts.js +6 -5
- package/package.json +9 -9
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry behind the swipe-to-dismiss gesture of `<Drawer>`, kept apart from the component so the
|
|
3
|
+
* calculations can be tested without a rendered drawer.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} SwipeSample
|
|
7
|
+
* @property {number} offset Distance the drawer has been dragged towards its edge, in pixels.
|
|
8
|
+
* @property {number} time Time of the sample in milliseconds, such as an event’s
|
|
9
|
+
* `timeStamp`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} SwipeState
|
|
13
|
+
* @property {number} pointerId ID of the pointer dragging the drawer.
|
|
14
|
+
* @property {'X' | 'Y'} axis Axis along which the drawer is dragged.
|
|
15
|
+
* @property {1 | -1} sign Sign of the direction towards the edge. See {@link getSwipeDirection}.
|
|
16
|
+
* @property {number} start Position along the axis where the drag has started, in pixels.
|
|
17
|
+
* @property {SwipeSample[]} samples Recent samples. See {@link addSwipeSample}.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Portion of the drawer’s size it has to be dragged by to be dismissed on release.
|
|
21
|
+
*/
|
|
22
|
+
export const SWIPE_DISMISS_DISTANCE_RATIO: 0.3;
|
|
23
|
+
/**
|
|
24
|
+
* Speed in pixels per millisecond towards the edge at which a release dismisses the drawer, however
|
|
25
|
+
* short the drag, so a quick flick is enough.
|
|
26
|
+
*/
|
|
27
|
+
export const SWIPE_DISMISS_VELOCITY: 0.5;
|
|
28
|
+
/**
|
|
29
|
+
* Distance in pixels a flick has to cover to dismiss the drawer, so a finger that rolls a little
|
|
30
|
+
* while tapping the header doesn’t count as one.
|
|
31
|
+
*/
|
|
32
|
+
export const SWIPE_FLICK_MIN_DISTANCE: 16;
|
|
33
|
+
/**
|
|
34
|
+
* Time window in milliseconds over which the speed of a drag is measured when it’s released.
|
|
35
|
+
*/
|
|
36
|
+
export const SWIPE_VELOCITY_WINDOW: 100;
|
|
37
|
+
export function getSwipeDirection(position: "top" | "right" | "bottom" | "left", rtl: boolean): {
|
|
38
|
+
axis: "X" | "Y";
|
|
39
|
+
sign: 1 | -1;
|
|
40
|
+
};
|
|
41
|
+
export function getSwipeOffset(delta: number, sign: 1 | -1): number;
|
|
42
|
+
export function addSwipeSample(samples: SwipeSample[], sample: SwipeSample): SwipeSample[];
|
|
43
|
+
export function getSwipeVelocity(samples: SwipeSample[]): number;
|
|
44
|
+
export function shouldDismissSwipe({ offset, size, velocity }: {
|
|
45
|
+
offset: number;
|
|
46
|
+
size: number;
|
|
47
|
+
velocity: number;
|
|
48
|
+
}): boolean;
|
|
49
|
+
export type SwipeSample = {
|
|
50
|
+
/**
|
|
51
|
+
* Distance the drawer has been dragged towards its edge, in pixels.
|
|
52
|
+
*/
|
|
53
|
+
offset: number;
|
|
54
|
+
/**
|
|
55
|
+
* Time of the sample in milliseconds, such as an event’s
|
|
56
|
+
* `timeStamp`.
|
|
57
|
+
*/
|
|
58
|
+
time: number;
|
|
59
|
+
};
|
|
60
|
+
export type SwipeState = {
|
|
61
|
+
/**
|
|
62
|
+
* ID of the pointer dragging the drawer.
|
|
63
|
+
*/
|
|
64
|
+
pointerId: number;
|
|
65
|
+
/**
|
|
66
|
+
* Axis along which the drawer is dragged.
|
|
67
|
+
*/
|
|
68
|
+
axis: "X" | "Y";
|
|
69
|
+
/**
|
|
70
|
+
* Sign of the direction towards the edge. See {@link getSwipeDirection}.
|
|
71
|
+
*/
|
|
72
|
+
sign: 1 | -1;
|
|
73
|
+
/**
|
|
74
|
+
* Position along the axis where the drag has started, in pixels.
|
|
75
|
+
*/
|
|
76
|
+
start: number;
|
|
77
|
+
/**
|
|
78
|
+
* Recent samples. See {@link addSwipeSample}.
|
|
79
|
+
*/
|
|
80
|
+
samples: SwipeSample[];
|
|
81
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry behind the swipe-to-dismiss gesture of `<Drawer>`, kept apart from the component so the
|
|
3
|
+
* calculations can be tested without a rendered drawer.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} SwipeSample
|
|
8
|
+
* @property {number} offset Distance the drawer has been dragged towards its edge, in pixels.
|
|
9
|
+
* @property {number} time Time of the sample in milliseconds, such as an event’s
|
|
10
|
+
* `timeStamp`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} SwipeState
|
|
15
|
+
* @property {number} pointerId ID of the pointer dragging the drawer.
|
|
16
|
+
* @property {'X' | 'Y'} axis Axis along which the drawer is dragged.
|
|
17
|
+
* @property {1 | -1} sign Sign of the direction towards the edge. See {@link getSwipeDirection}.
|
|
18
|
+
* @property {number} start Position along the axis where the drag has started, in pixels.
|
|
19
|
+
* @property {SwipeSample[]} samples Recent samples. See {@link addSwipeSample}.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Portion of the drawer’s size it has to be dragged by to be dismissed on release.
|
|
24
|
+
*/
|
|
25
|
+
export const SWIPE_DISMISS_DISTANCE_RATIO = 0.3;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Speed in pixels per millisecond towards the edge at which a release dismisses the drawer, however
|
|
29
|
+
* short the drag, so a quick flick is enough.
|
|
30
|
+
*/
|
|
31
|
+
export const SWIPE_DISMISS_VELOCITY = 0.5;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Distance in pixels a flick has to cover to dismiss the drawer, so a finger that rolls a little
|
|
35
|
+
* while tapping the header doesn’t count as one.
|
|
36
|
+
*/
|
|
37
|
+
export const SWIPE_FLICK_MIN_DISTANCE = 16;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Time window in milliseconds over which the speed of a drag is measured when it’s released.
|
|
41
|
+
*/
|
|
42
|
+
export const SWIPE_VELOCITY_WINDOW = 100;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the axis along which the drawer is swiped away, and the sign of the physical direction
|
|
46
|
+
* towards the edge it’s attached to.
|
|
47
|
+
* @param {'top' | 'right' | 'bottom' | 'left'} position Position of the drawer.
|
|
48
|
+
* @param {boolean} rtl Whether the layout is right-to-left, which mirrors the `left` and `right`
|
|
49
|
+
* positions: they’re logical, like the `inset-inline` properties they’re laid out with.
|
|
50
|
+
* @returns {{ axis: 'X' | 'Y', sign: 1 | -1 }} Axis, named as in `translateX()` and
|
|
51
|
+
* `translateY()`, and sign.
|
|
52
|
+
*/
|
|
53
|
+
export const getSwipeDirection = (position, rtl) => {
|
|
54
|
+
if (position === 'top' || position === 'bottom') {
|
|
55
|
+
return { axis: 'Y', sign: position === 'bottom' ? 1 : -1 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { axis: 'X', sign: (position === 'right') !== rtl ? 1 : -1 };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the distance the drawer has been dragged towards its edge. A drag the other way is ignored:
|
|
63
|
+
* the drawer can’t be pulled out further than it’s open.
|
|
64
|
+
* @param {number} delta Physical distance the pointer has moved along the axis, in pixels.
|
|
65
|
+
* @param {1 | -1} sign Sign of the direction towards the edge. See {@link getSwipeDirection}.
|
|
66
|
+
* @returns {number} Offset in pixels, `0` or more.
|
|
67
|
+
*/
|
|
68
|
+
export const getSwipeOffset = (delta, sign) => Math.max(0, delta * sign);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Add a sample to the list the speed of a drag is measured with, dropping the ones that have left
|
|
72
|
+
* the measuring window.
|
|
73
|
+
* @param {SwipeSample[]} samples Previous samples, oldest first.
|
|
74
|
+
* @param {SwipeSample} sample New sample.
|
|
75
|
+
* @returns {SwipeSample[]} Samples within {@link SWIPE_VELOCITY_WINDOW}, oldest first.
|
|
76
|
+
*/
|
|
77
|
+
export const addSwipeSample = (samples, sample) => [
|
|
78
|
+
...samples.filter(({ time }) => sample.time - time <= SWIPE_VELOCITY_WINDOW),
|
|
79
|
+
sample,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the speed of a drag towards the edge.
|
|
84
|
+
* @param {SwipeSample[]} samples Recent samples, oldest first. See {@link addSwipeSample}.
|
|
85
|
+
* @returns {number} Speed in pixels per millisecond, negative if the drag was heading back.
|
|
86
|
+
*/
|
|
87
|
+
export const getSwipeVelocity = (samples) => {
|
|
88
|
+
const first = samples[0];
|
|
89
|
+
const last = samples[samples.length - 1];
|
|
90
|
+
|
|
91
|
+
if (!first || !last || last.time <= first.time) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (last.offset - first.offset) / (last.time - first.time);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check whether a drag that has just been released should dismiss the drawer: it has been dragged
|
|
100
|
+
* far enough, or flicked towards its edge.
|
|
101
|
+
* @param {object} args Arguments.
|
|
102
|
+
* @param {number} args.offset Distance the drawer has been dragged towards its edge, in pixels.
|
|
103
|
+
* @param {number} args.size Size of the drawer along the axis, in pixels.
|
|
104
|
+
* @param {number} args.velocity Speed of the drag towards the edge, in pixels per millisecond.
|
|
105
|
+
* @returns {boolean} Result.
|
|
106
|
+
*/
|
|
107
|
+
export const shouldDismissSwipe = ({ offset, size, velocity }) =>
|
|
108
|
+
offset > 0 &&
|
|
109
|
+
(offset >= size * SWIPE_DISMISS_DISTANCE_RATIO ||
|
|
110
|
+
(offset >= SWIPE_FLICK_MIN_DISTANCE && velocity >= SWIPE_DISMISS_VELOCITY));
|
|
@@ -6,14 +6,23 @@
|
|
|
6
6
|
-->
|
|
7
7
|
<script>
|
|
8
8
|
import { _ } from '@sveltia/i18n';
|
|
9
|
+
import { untrack } from 'svelte';
|
|
9
10
|
import Button from '../button/button.svelte';
|
|
10
11
|
import Spacer from '../divider/spacer.svelte';
|
|
11
12
|
import Icon from '../icon/icon.svelte';
|
|
12
13
|
import Modal from '../util/modal.svelte';
|
|
14
|
+
import {
|
|
15
|
+
addSwipeSample,
|
|
16
|
+
getSwipeDirection,
|
|
17
|
+
getSwipeOffset,
|
|
18
|
+
getSwipeVelocity,
|
|
19
|
+
shouldDismissSwipe,
|
|
20
|
+
} from './drawer.js';
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
23
|
* @import { Snippet } from 'svelte';
|
|
16
24
|
* @import { ModalProps } from '../../typedefs';
|
|
25
|
+
* @import { SwipeState } from './drawer.js';
|
|
17
26
|
*/
|
|
18
27
|
|
|
19
28
|
/**
|
|
@@ -29,6 +38,11 @@
|
|
|
29
38
|
* @property {'small' | 'medium' | 'large' | 'x-large' | 'full'} [size] Width or height of the
|
|
30
39
|
* drawer.
|
|
31
40
|
* @property {'inside' | 'outside' | false} [showClose] Whether to show the Close button.
|
|
41
|
+
* @property {boolean} [swipeDismiss] Whether the drawer can be dismissed by dragging it towards
|
|
42
|
+
* the edge it’s attached to, as a bottom sheet on a mobile device is. The drag starts from the
|
|
43
|
+
* header, or from a grab handle shown on a top or bottom drawer; the main content and the footer
|
|
44
|
+
* are left alone, so they can be scrolled and interacted with. The Close button and the Escape
|
|
45
|
+
* key still work, as the gesture isn’t available to everyone.
|
|
32
46
|
* @property {Snippet} [children] Primary slot content.
|
|
33
47
|
* @property {Snippet} [header] Header slot content.
|
|
34
48
|
* @property {Snippet} [headerExtra] Header extra slot content.
|
|
@@ -49,6 +63,7 @@
|
|
|
49
63
|
position = 'right',
|
|
50
64
|
size = 'small',
|
|
51
65
|
showClose = 'outside',
|
|
66
|
+
swipeDismiss = false,
|
|
52
67
|
children,
|
|
53
68
|
header,
|
|
54
69
|
headerExtra,
|
|
@@ -58,6 +73,23 @@
|
|
|
58
73
|
/* eslint-enable prefer-const */
|
|
59
74
|
} = $props();
|
|
60
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Elements a drag can’t start from: the parts of the drawer that can scroll or hold text to
|
|
78
|
+
* select, and anything interactive.
|
|
79
|
+
*/
|
|
80
|
+
const EXCLUDED_SWIPE_TARGETS = [
|
|
81
|
+
'.main',
|
|
82
|
+
'.footer',
|
|
83
|
+
'.extra-control',
|
|
84
|
+
'a',
|
|
85
|
+
'button',
|
|
86
|
+
'input',
|
|
87
|
+
'select',
|
|
88
|
+
'textarea',
|
|
89
|
+
'[contenteditable]',
|
|
90
|
+
'[tabindex]',
|
|
91
|
+
].join(', ');
|
|
92
|
+
|
|
61
93
|
/**
|
|
62
94
|
* The ID of the drawer.
|
|
63
95
|
* @type {string}
|
|
@@ -74,6 +106,109 @@
|
|
|
74
106
|
position === 'right' || position === 'left' ? 'vertical' : 'horizontal',
|
|
75
107
|
);
|
|
76
108
|
|
|
109
|
+
/**
|
|
110
|
+
* A reference to the content element.
|
|
111
|
+
* @type {HTMLElement | undefined}
|
|
112
|
+
*/
|
|
113
|
+
let content = $state();
|
|
114
|
+
/**
|
|
115
|
+
* The drag in progress, if any. The object is replaced rather than updated, except for the
|
|
116
|
+
* samples, which only feed the speed calculation on release, so nothing needs to track them.
|
|
117
|
+
* @type {SwipeState | undefined}
|
|
118
|
+
*/
|
|
119
|
+
let swipe = $state.raw();
|
|
120
|
+
/**
|
|
121
|
+
* Distance the drawer has been dragged towards its edge, in pixels.
|
|
122
|
+
* @type {number}
|
|
123
|
+
*/
|
|
124
|
+
let swipeOffset = $state(0);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle the `pointerdown` event on the content, starting a drag from anywhere but the main
|
|
128
|
+
* content, the footer and the controls.
|
|
129
|
+
* @param {PointerEvent} event `pointerdown` event.
|
|
130
|
+
*/
|
|
131
|
+
const onPointerDown = (event) => {
|
|
132
|
+
const { button, isPrimary, pointerId, target, clientX, clientY } = event;
|
|
133
|
+
|
|
134
|
+
if (!swipeDismiss || !content || swipe || button !== 0 || !isPrimary) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The dialog around the content is focusable too, so only look inside the content
|
|
139
|
+
const excluded = /** @type {Element} */ (target).closest(EXCLUDED_SWIPE_TARGETS);
|
|
140
|
+
|
|
141
|
+
if (excluded && content.contains(excluded)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { axis, sign } = getSwipeDirection(position, content.matches(':dir(rtl)'));
|
|
146
|
+
|
|
147
|
+
swipe = { pointerId, axis, sign, start: axis === 'Y' ? clientY : clientX, samples: [] };
|
|
148
|
+
swipeOffset = 0;
|
|
149
|
+
content.setPointerCapture(pointerId);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Handle the `pointermove` event on the content while dragging.
|
|
154
|
+
* @param {PointerEvent} event `pointermove` event.
|
|
155
|
+
*/
|
|
156
|
+
const onPointerMove = (event) => {
|
|
157
|
+
const { pointerId, clientX, clientY, timeStamp } = event;
|
|
158
|
+
|
|
159
|
+
if (!swipe || pointerId !== swipe.pointerId) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
swipeOffset = getSwipeOffset(
|
|
164
|
+
(swipe.axis === 'Y' ? clientY : clientX) - swipe.start,
|
|
165
|
+
swipe.sign,
|
|
166
|
+
);
|
|
167
|
+
swipe.samples = addSwipeSample(swipe.samples, { offset: swipeOffset, time: timeStamp });
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Handle the `pointerup` and `pointercancel` events on the content, ending the drag. The drawer
|
|
172
|
+
* is dismissed if it has been dragged far enough or flicked, or goes back into place otherwise.
|
|
173
|
+
* Either way, the transition picks up from where the drag has left the drawer.
|
|
174
|
+
* @param {PointerEvent} event `pointerup` or `pointercancel` event.
|
|
175
|
+
*/
|
|
176
|
+
const onPointerUp = (event) => {
|
|
177
|
+
if (!swipe || !content || event.pointerId !== swipe.pointerId) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { width, height } = content.getBoundingClientRect();
|
|
182
|
+
// The pointer may have been held still since it last moved, which ends any flick
|
|
183
|
+
const samples = addSwipeSample(swipe.samples, { offset: swipeOffset, time: event.timeStamp });
|
|
184
|
+
|
|
185
|
+
const dismiss =
|
|
186
|
+
event.type === 'pointerup' &&
|
|
187
|
+
shouldDismissSwipe({
|
|
188
|
+
offset: swipeOffset,
|
|
189
|
+
size: swipe.axis === 'Y' ? height : width,
|
|
190
|
+
velocity: getSwipeVelocity(samples),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
swipe = undefined;
|
|
194
|
+
swipeOffset = 0;
|
|
195
|
+
|
|
196
|
+
if (dismiss) {
|
|
197
|
+
modal?.close('close');
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
$effect(() => {
|
|
202
|
+
if (!open) {
|
|
203
|
+
// The drawer can be closed in the middle of a drag, e.g. with the Escape key, and should
|
|
204
|
+
// then slide away rather than stay where the drag has left it
|
|
205
|
+
untrack(() => {
|
|
206
|
+
swipe = undefined;
|
|
207
|
+
swipeOffset = 0;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
77
212
|
/**
|
|
78
213
|
* Accessible name for the `<dialog>`, resolved the same way as `<Dialog>`: the built-in header
|
|
79
214
|
* renders the title in an element the dialog can point at; with a custom header the title is
|
|
@@ -93,7 +228,27 @@
|
|
|
93
228
|
aria-labelledby={labelledby}
|
|
94
229
|
showBackdrop
|
|
95
230
|
>
|
|
96
|
-
<div
|
|
231
|
+
<div
|
|
232
|
+
bind:this={content}
|
|
233
|
+
role="none"
|
|
234
|
+
class={[
|
|
235
|
+
'content',
|
|
236
|
+
className,
|
|
237
|
+
size,
|
|
238
|
+
position,
|
|
239
|
+
orientation,
|
|
240
|
+
{ 'swipe-dismiss': swipeDismiss, swiping: !!swipe },
|
|
241
|
+
]}
|
|
242
|
+
style:transform={swipe ? `translate${swipe.axis}(${swipe.sign * swipeOffset}px)` : undefined}
|
|
243
|
+
style:transition-duration={swipe ? '0s' : undefined}
|
|
244
|
+
onpointerdown={onPointerDown}
|
|
245
|
+
onpointermove={onPointerMove}
|
|
246
|
+
onpointerup={onPointerUp}
|
|
247
|
+
onpointercancel={onPointerUp}
|
|
248
|
+
>
|
|
249
|
+
{#if swipeDismiss && position === 'bottom'}
|
|
250
|
+
<div role="none" class="handle"></div>
|
|
251
|
+
{/if}
|
|
97
252
|
<div role="none" class="extra-control">
|
|
98
253
|
{#if showClose === 'outside'}
|
|
99
254
|
<Button
|
|
@@ -157,6 +312,9 @@
|
|
|
157
312
|
{@render footer?.()}
|
|
158
313
|
</div>
|
|
159
314
|
{/if}
|
|
315
|
+
{#if swipeDismiss && position === 'top'}
|
|
316
|
+
<div role="none" class="handle"></div>
|
|
317
|
+
{/if}
|
|
160
318
|
</div>
|
|
161
319
|
</Modal>
|
|
162
320
|
|
|
@@ -323,6 +481,30 @@
|
|
|
323
481
|
max-height: 100dvh;
|
|
324
482
|
}
|
|
325
483
|
|
|
484
|
+
.content.swipe-dismiss > :global(:not(.main, .footer, .extra-control)) {
|
|
485
|
+
touch-action: none;
|
|
486
|
+
user-select: none;
|
|
487
|
+
}
|
|
488
|
+
.content.swipe-dismiss.swiping, .content.swipe-dismiss.swiping .handle {
|
|
489
|
+
cursor: grabbing;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.handle {
|
|
493
|
+
flex: none;
|
|
494
|
+
display: flex;
|
|
495
|
+
justify-content: center;
|
|
496
|
+
padding: 8px 0;
|
|
497
|
+
cursor: grab;
|
|
498
|
+
}
|
|
499
|
+
.handle::before {
|
|
500
|
+
content: "";
|
|
501
|
+
border-radius: 2px;
|
|
502
|
+
width: 32px;
|
|
503
|
+
height: 4px;
|
|
504
|
+
background-color: var(--sui-drawer-handle-color, var(--sui-secondary-foreground-color));
|
|
505
|
+
opacity: 0.5;
|
|
506
|
+
}
|
|
507
|
+
|
|
326
508
|
:is(.header, .footer) {
|
|
327
509
|
display: flex;
|
|
328
510
|
align-items: center;
|
|
@@ -41,6 +41,14 @@ declare const Drawer: import("svelte").Component<ModalProps & {
|
|
|
41
41
|
* Whether to show the Close button.
|
|
42
42
|
*/
|
|
43
43
|
showClose?: false | "inside" | "outside" | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the drawer can be dismissed by dragging it towards
|
|
46
|
+
* the edge it’s attached to, as a bottom sheet on a mobile device is. The drag starts from the
|
|
47
|
+
* header, or from a grab handle shown on a top or bottom drawer; the main content and the footer
|
|
48
|
+
* are left alone, so they can be scrolled and interacted with. The Close button and the Escape
|
|
49
|
+
* key still work, as the gesture isn’t available to everyone.
|
|
50
|
+
*/
|
|
51
|
+
swipeDismiss?: boolean | undefined;
|
|
44
52
|
/**
|
|
45
53
|
* Primary slot content.
|
|
46
54
|
*/
|
|
@@ -99,6 +107,14 @@ type Props = {
|
|
|
99
107
|
* Whether to show the Close button.
|
|
100
108
|
*/
|
|
101
109
|
showClose?: false | "inside" | "outside" | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Whether the drawer can be dismissed by dragging it towards
|
|
112
|
+
* the edge it’s attached to, as a bottom sheet on a mobile device is. The drag starts from the
|
|
113
|
+
* header, or from a grab handle shown on a top or bottom drawer; the main content and the footer
|
|
114
|
+
* are left alone, so they can be scrolled and interacted with. The Close button and the Escape
|
|
115
|
+
* key still work, as the gesture isn’t available to everyone.
|
|
116
|
+
*/
|
|
117
|
+
swipeDismiss?: boolean | undefined;
|
|
102
118
|
/**
|
|
103
119
|
* Primary slot content.
|
|
104
120
|
*/
|
|
@@ -129,7 +129,8 @@ export const getFontFaceDescriptors = ({ weight, display, unicodeRange, sizeAdju
|
|
|
129
129
|
* Call it before the `AppShell` component is mounted, so no font is fetched from the CDN first.
|
|
130
130
|
* @param {Record<string, string>} urls URLs keyed with the Fontsource file name, e.g.
|
|
131
131
|
* `source-sans-3-latin-wght-normal.woff2`. Any font omitted, or whose file fails to load, is still
|
|
132
|
-
* loaded from the CDN
|
|
132
|
+
* loaded from the CDN, unless the app has removed the `@font-face` rules for it; a failure is
|
|
133
|
+
* reported in the console.
|
|
133
134
|
*/
|
|
134
135
|
export const setFontURLs = (urls) => {
|
|
135
136
|
if (typeof document === 'undefined' || !document.fonts) {
|
|
@@ -153,13 +154,13 @@ export const setFontURLs = (urls) => {
|
|
|
153
154
|
|
|
154
155
|
document.fonts.add(fontFace);
|
|
155
156
|
|
|
156
|
-
// The browser falls back to the `@font-face` rule for the CDN if the file can’t be loaded,
|
|
157
|
-
//
|
|
158
|
-
// download
|
|
157
|
+
// The browser falls back to the `@font-face` rule for the CDN if the file can’t be loaded, or
|
|
158
|
+
// to another font if the app has removed the rule, which goes unnoticed otherwise. `loaded`
|
|
159
|
+
// settles once the font is used, without forcing a download
|
|
159
160
|
fontFace.loaded.catch(() => {
|
|
160
161
|
// eslint-disable-next-line no-console
|
|
161
162
|
console.warn(
|
|
162
|
-
`Failed to load the ${font.family} font from ${url}.
|
|
163
|
+
`Failed to load the ${font.family} font from ${url}. A fallback font is used instead.`,
|
|
163
164
|
);
|
|
164
165
|
});
|
|
165
166
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltia/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.1",
|
|
4
4
|
"description": "A collection of Svelte components and utilities for building user interfaces.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
69
69
|
"@sveltejs/kit": "^2.70.3",
|
|
70
70
|
"@sveltejs/package": "^2.5.8",
|
|
71
|
-
"@sveltejs/vite-plugin-svelte": "^7.3.
|
|
72
|
-
"@vitest/browser-playwright": "^5.0.
|
|
73
|
-
"@vitest/coverage-v8": "^5.0.
|
|
74
|
-
"cspell": "^10.3.
|
|
71
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
72
|
+
"@vitest/browser-playwright": "^5.0.2",
|
|
73
|
+
"@vitest/coverage-v8": "^5.0.2",
|
|
74
|
+
"cspell": "^10.3.4",
|
|
75
75
|
"eslint": "^9.39.5",
|
|
76
76
|
"eslint-config-airbnb-extended": "^3.2.0",
|
|
77
77
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
"playwright": "^1.63.0",
|
|
86
86
|
"postcss": "^8.5.28",
|
|
87
87
|
"postcss-html": "^2.0.0",
|
|
88
|
-
"prettier": "^3.9.
|
|
88
|
+
"prettier": "^3.9.9",
|
|
89
89
|
"prettier-plugin-svelte": "^4.1.1",
|
|
90
|
-
"rolldown": "^1.2.
|
|
90
|
+
"rolldown": "^1.2.11",
|
|
91
91
|
"sass": "^1.105.0",
|
|
92
92
|
"shiki": "^4.4.3",
|
|
93
93
|
"stylelint": "^17.15.0",
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
"svelte-check": "^4.7.6",
|
|
98
98
|
"svelte-preprocess": "^6.0.5",
|
|
99
99
|
"tslib": "^2.8.1",
|
|
100
|
-
"vite": "^8.3.
|
|
101
|
-
"vitest": "^5.0.
|
|
100
|
+
"vite": "^8.3.1",
|
|
101
|
+
"vitest": "^5.0.2",
|
|
102
102
|
"vitest-browser-svelte": "^3.1.0"
|
|
103
103
|
},
|
|
104
104
|
"peerDependencies": {
|