@axium/client 0.30.0 → 0.31.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/assets/styles.css +56 -3
- package/dist/gui/index.d.ts +1 -0
- package/dist/gui/index.js +1 -0
- package/dist/gui/mobile.d.ts +1 -0
- package/dist/gui/mobile.js +4 -0
- package/lib/Popover.svelte +10 -10
- package/lib/UserMenu.svelte +10 -7
- package/lib/attachments/context-menu.ts +4 -1
- package/lib/attachments/drag-and-drop.ts +191 -200
- package/package.json +5 -2
- package/styles/list.css +1 -1
- package/styles/styles.d.ts +1 -0
package/assets/styles.css
CHANGED
|
@@ -293,16 +293,17 @@ div.success {
|
|
|
293
293
|
|
|
294
294
|
.drag-indicator {
|
|
295
295
|
position: fixed;
|
|
296
|
+
top: 0;
|
|
297
|
+
left: 0;
|
|
296
298
|
z-index: 100;
|
|
299
|
+
pointer-events: none;
|
|
297
300
|
display: inline-flex;
|
|
298
301
|
align-items: center;
|
|
299
302
|
gap: 0.5em;
|
|
300
|
-
pointer-events: none;
|
|
301
303
|
padding: 0.5em 0.75em;
|
|
302
304
|
border-radius: 0.5em;
|
|
303
305
|
background-color: var(--bg-elevated);
|
|
304
306
|
border: var(--border-elevated);
|
|
305
|
-
transform: translate(0.75em, 0.75em);
|
|
306
307
|
max-width: 20em;
|
|
307
308
|
white-space: nowrap;
|
|
308
309
|
|
|
@@ -326,9 +327,26 @@ div.success {
|
|
|
326
327
|
border-radius: 0.5em;
|
|
327
328
|
}
|
|
328
329
|
|
|
330
|
+
.drag-upload-indicator {
|
|
331
|
+
position: fixed;
|
|
332
|
+
bottom: 2em;
|
|
333
|
+
left: 50%;
|
|
334
|
+
translate: -50% 0;
|
|
335
|
+
z-index: 100;
|
|
336
|
+
pointer-events: none;
|
|
337
|
+
display: inline-flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
gap: 0.5em;
|
|
340
|
+
padding: 1em 2em;
|
|
341
|
+
border-radius: 1em;
|
|
342
|
+
background-color: var(--bg-elevated);
|
|
343
|
+
border: var(--border-accent);
|
|
344
|
+
font-weight: bold;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
}
|
|
347
|
+
|
|
329
348
|
body.dragging-items,
|
|
330
349
|
body.dragging-items * {
|
|
331
|
-
cursor: grabbing !important;
|
|
332
350
|
user-select: none;
|
|
333
351
|
}
|
|
334
352
|
|
|
@@ -456,4 +474,39 @@ div.toast {
|
|
|
456
474
|
z-index: 4;
|
|
457
475
|
font-size: 1.25em;
|
|
458
476
|
}
|
|
477
|
+
|
|
478
|
+
:popover-open {
|
|
479
|
+
position: fixed;
|
|
480
|
+
width: 100%;
|
|
481
|
+
max-width: 100%;
|
|
482
|
+
max-height: 80dvh;
|
|
483
|
+
overflow-y: auto;
|
|
484
|
+
border: none;
|
|
485
|
+
gap: 0.25em;
|
|
486
|
+
|
|
487
|
+
&:not(.mobile-top) {
|
|
488
|
+
inset: auto 0 0 0 !important;
|
|
489
|
+
border-top: var(--border-elevated);
|
|
490
|
+
border-radius: 1em 1em 0 0;
|
|
491
|
+
padding: 0.5em 0.5em calc(0.5em + env(safe-area-inset-bottom, 0px));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
&.mobile-top {
|
|
495
|
+
inset: 0 0 auto 0 !important;
|
|
496
|
+
border-bottom: var(--border-elevated);
|
|
497
|
+
border-radius: 0 0 1em 1em;
|
|
498
|
+
padding: calc(0.5em + env(safe-area-inset-top, 0px)) 0.5em 0.5em;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.menu-item {
|
|
502
|
+
padding: 1em;
|
|
503
|
+
font-size: 1.05em;
|
|
504
|
+
border-radius: 0.75em;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.menu-header {
|
|
508
|
+
padding: 0.75em 1em 0.5em;
|
|
509
|
+
font-size: 1em;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
459
512
|
}
|
package/dist/gui/index.d.ts
CHANGED
package/dist/gui/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isMobile(): boolean;
|
package/lib/Popover.svelte
CHANGED
|
@@ -66,16 +66,16 @@
|
|
|
66
66
|
:global(:hover:has(:hover > .Popover > .toggle-hover)) > div > .toggle-hover:not(.cascade-hover) {
|
|
67
67
|
visibility: hidden;
|
|
68
68
|
}
|
|
69
|
-
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
:popover-open {
|
|
71
|
+
position-anchor: --popover;
|
|
72
|
+
position-try-fallbacks:
|
|
73
|
+
flip-inline,
|
|
74
|
+
flip-block,
|
|
75
|
+
flip-block flip-inline;
|
|
76
|
+
position-visibility: always;
|
|
77
|
+
left: anchor(left);
|
|
78
|
+
top: anchor(bottom);
|
|
79
|
+
}
|
|
80
80
|
}
|
|
81
81
|
</style>
|
package/lib/UserMenu.svelte
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
{#if user}
|
|
13
|
-
<Popover>
|
|
13
|
+
<Popover class="mobile-top">
|
|
14
14
|
{#snippet toggle()}
|
|
15
15
|
<div class="UserMenu toggle">
|
|
16
16
|
<UserPFP {user} />
|
|
@@ -79,12 +79,15 @@
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
:global(.UserMenu + div:popover-open) {
|
|
82
|
-
position: fixed;
|
|
83
|
-
left: unset;
|
|
84
|
-
right: anchor(right);
|
|
85
|
-
top: calc(anchor(bottom) + 0.5em);
|
|
86
|
-
width: fit-content;
|
|
87
|
-
height: fit-content;
|
|
88
82
|
cursor: default;
|
|
83
|
+
|
|
84
|
+
@media (width > 700px) {
|
|
85
|
+
position: fixed;
|
|
86
|
+
left: unset;
|
|
87
|
+
right: anchor(right);
|
|
88
|
+
top: calc(anchor(bottom) + 0.5em);
|
|
89
|
+
width: fit-content;
|
|
90
|
+
height: fit-content;
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
</style>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isMobile } from '@axium/client/gui';
|
|
1
2
|
import { mount, unmount } from 'svelte';
|
|
2
3
|
import type { Attachment } from 'svelte/attachments';
|
|
3
4
|
import Icon from '../Icon.svelte';
|
|
@@ -54,7 +55,7 @@ export function contextMenu(...menuItems: ContextMenuEntry[] | [() => ContextMen
|
|
|
54
55
|
item.action();
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
if (item.i) mountedIcons.add(mount
|
|
58
|
+
if (item.i) mountedIcons.add(mount(Icon, { target: div, props: { i: item.i } }));
|
|
58
59
|
|
|
59
60
|
div.appendChild(document.createTextNode(item.text));
|
|
60
61
|
|
|
@@ -86,6 +87,8 @@ export function contextMenu(...menuItems: ContextMenuEntry[] | [() => ContextMen
|
|
|
86
87
|
menu.showPopover();
|
|
87
88
|
_forcePopover = true;
|
|
88
89
|
|
|
90
|
+
if (isMobile()) return;
|
|
91
|
+
|
|
89
92
|
const x = e.clientX;
|
|
90
93
|
const y = e.clientY;
|
|
91
94
|
|
|
@@ -1,257 +1,248 @@
|
|
|
1
1
|
import { mount, unmount } from 'svelte';
|
|
2
2
|
import type { Attachment } from 'svelte/attachments';
|
|
3
3
|
import Icon from '../Icon.svelte';
|
|
4
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
5
|
-
import type { Selection } from './selection.js';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const mimeType = 'text/x-axium-drag';
|
|
6
|
+
|
|
7
|
+
export interface Display {
|
|
10
8
|
name: string;
|
|
11
9
|
icon?: string;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* Things are made grabbable with the {@link source} attachment,
|
|
18
|
-
* and targets accept them with {@link target}.
|
|
19
|
-
*
|
|
20
|
-
* While a drag is in progress a floating indicator follows the pointer showing
|
|
21
|
-
* the grabbed item's name and icon, with a bold `+ N` when several are grabbed.
|
|
22
|
-
*
|
|
23
|
-
* The grabbed elements expose the `dragging` class for styling.
|
|
24
|
-
*/
|
|
25
|
-
export class Controller extends SvelteSet<string> implements Disposable {
|
|
26
|
-
#indicator?: HTMLElement;
|
|
27
|
-
#indicatorIcon?: Record<string, any>;
|
|
12
|
+
function createIndicator(display: Display, extraItems: number): HTMLElement {
|
|
13
|
+
const indicator = document.createElement('div');
|
|
14
|
+
indicator.className = 'drag-indicator';
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
readonly #targets = new Map<HTMLElement, (ids: string[]) => unknown>();
|
|
16
|
+
const icon = display.icon && mount(Icon, { target: indicator, props: { i: display.icon } });
|
|
31
17
|
|
|
32
|
-
|
|
18
|
+
const name = document.createElement('span');
|
|
19
|
+
name.className = 'drag-name';
|
|
20
|
+
name.textContent = display.name;
|
|
21
|
+
indicator.appendChild(name);
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
if (extraItems) {
|
|
24
|
+
const extra = document.createElement('b');
|
|
25
|
+
extra.className = 'drag-count';
|
|
26
|
+
extra.textContent = `+ ${extraItems}`;
|
|
27
|
+
indicator.appendChild(extra);
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
this.#targets.set(element, onDrop);
|
|
41
|
-
return () => this.#targets.delete(element);
|
|
42
|
-
}
|
|
30
|
+
document.body.appendChild(indicator);
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const name = document.createElement('span');
|
|
54
|
-
name.className = 'drag-name';
|
|
55
|
-
name.textContent = lead.name;
|
|
56
|
-
indicator.appendChild(name);
|
|
57
|
-
|
|
58
|
-
if (ids.length > 1) {
|
|
59
|
-
const extra = document.createElement('b');
|
|
60
|
-
extra.className = 'drag-count';
|
|
61
|
-
extra.textContent = `+ ${ids.length - 1}`;
|
|
62
|
-
indicator.appendChild(extra);
|
|
63
|
-
}
|
|
32
|
+
const remove = indicator.remove.bind(indicator);
|
|
33
|
+
return Object.assign(indicator, {
|
|
34
|
+
remove() {
|
|
35
|
+
if (icon) unmount(icon);
|
|
36
|
+
remove();
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
document.body.classList.add('dragging-items');
|
|
68
|
-
}
|
|
41
|
+
const emptyImage = new Image();
|
|
42
|
+
emptyImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
move(x: number, y: number): void {
|
|
72
|
-
if (!this.#indicator) return;
|
|
73
|
-
this.#indicator.style.left = x + 'px';
|
|
74
|
-
this.#indicator.style.top = y + 'px';
|
|
75
|
-
|
|
76
|
-
// Find the drop target under the pointer (ignoring the indicator itself, which is pointer-events: none).
|
|
77
|
-
let target: HTMLElement | undefined;
|
|
78
|
-
for (let node = document.elementFromPoint(x, y) as HTMLElement | null; node; node = node.parentElement) {
|
|
79
|
-
if (this.#targets.has(node)) {
|
|
80
|
-
target = node;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
44
|
+
export let isActive = false;
|
|
84
45
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Make an element a draggable source.
|
|
48
|
+
*
|
|
49
|
+
* Grabs either the given selection (when non-empty) or just this item.
|
|
50
|
+
*/
|
|
51
|
+
export function source<T = string>(type: string, selection: Iterable<T>, thisValue: T, display: Display): Attachment<HTMLElement> {
|
|
52
|
+
const sel = Array.from(selection);
|
|
53
|
+
if (!sel.length) sel.push(thisValue);
|
|
54
|
+
|
|
55
|
+
return function _attachDraggable(element: HTMLElement) {
|
|
56
|
+
element.draggable = true;
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (onDrop && this.size) onDrop([...this]);
|
|
96
|
-
} finally {
|
|
97
|
-
this.dispose();
|
|
58
|
+
let indicator: HTMLElement;
|
|
59
|
+
|
|
60
|
+
function onDragOver(e: DragEvent) {
|
|
61
|
+
indicator.style.translate = `${e.clientX}px ${e.clientY}px`;
|
|
98
62
|
}
|
|
99
|
-
}
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
64
|
+
function onDragStart(e: DragEvent) {
|
|
65
|
+
for (let node = e.target as HTMLElement | null; node && node !== element; node = node.parentElement) {
|
|
66
|
+
if (node.hasAttribute('data-no-select')) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
105
71
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.#activeTarget = undefined;
|
|
110
|
-
if (this.#indicatorIcon) unmount(this.#indicatorIcon);
|
|
111
|
-
this.#indicatorIcon = undefined;
|
|
112
|
-
this.#indicator?.remove();
|
|
113
|
-
this.#indicator = undefined;
|
|
114
|
-
document.body.classList.remove('dragging-items');
|
|
115
|
-
}
|
|
72
|
+
if (!e.dataTransfer) return;
|
|
73
|
+
e.dataTransfer.setData(`${mimeType}+${type.toLowerCase()}`, JSON.stringify(sel));
|
|
74
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
116
75
|
|
|
117
|
-
|
|
118
|
-
this.dispose();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
76
|
+
e.dataTransfer.setDragImage(emptyImage, 0, 0);
|
|
121
77
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
78
|
+
isActive = true;
|
|
79
|
+
indicator = createIndicator(display, sel.length - 1);
|
|
80
|
+
indicator.style.translate = `${e.clientX}px ${e.clientY}px`;
|
|
81
|
+
document.body.classList.add('dragging-items');
|
|
82
|
+
document.addEventListener('dragover', onDragOver);
|
|
83
|
+
}
|
|
126
84
|
|
|
127
|
-
|
|
85
|
+
function onDragEnd() {
|
|
86
|
+
isActive = false;
|
|
87
|
+
document.removeEventListener('dragover', onDragOver);
|
|
88
|
+
indicator.remove();
|
|
89
|
+
document.body.classList.remove('dragging-items');
|
|
90
|
+
}
|
|
128
91
|
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
element.addEventListener('dragstart', onDragStart);
|
|
93
|
+
element.addEventListener('dragend', onDragEnd);
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
document.removeEventListener('dragover', onDragOver);
|
|
97
|
+
element.removeEventListener('dragstart', onDragStart);
|
|
98
|
+
element.removeEventListener('dragend', onDragEnd);
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
}
|
|
131
102
|
|
|
132
103
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* Pressing and dragging the pointer out of the item grabs either the current {@link Selection} (when
|
|
136
|
-
* this item is part of a non-empty selection) or just this item, then follows the pointer until release.
|
|
104
|
+
* Register an element as a drop target for drags of the given kind.
|
|
137
105
|
*/
|
|
138
|
-
export function
|
|
139
|
-
|
|
140
|
-
selectedIds: Iterable<string> = [item.id],
|
|
141
|
-
controller: Controller = defaultController
|
|
142
|
-
): Attachment<HTMLElement> {
|
|
143
|
-
const selection = Array.from(selectedIds);
|
|
144
|
-
if (!selection.length) selection.push(item.id);
|
|
106
|
+
export function target<T = string>(type: string, onDrop: (items: T[]) => unknown, exclude?: T): Attachment<HTMLElement> {
|
|
107
|
+
const mime = `${mimeType}+${type.toLowerCase()}`;
|
|
145
108
|
|
|
146
|
-
return function
|
|
147
|
-
let
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
holdTimer: number | undefined;
|
|
152
|
-
|
|
153
|
-
function _clearTimer() {
|
|
154
|
-
clearTimeout(holdTimer);
|
|
155
|
-
holdTimer = undefined;
|
|
156
|
-
pointerId = undefined;
|
|
109
|
+
return function _attachDropTarget(element: HTMLElement) {
|
|
110
|
+
let depth = 0;
|
|
111
|
+
|
|
112
|
+
function ignore(e: DragEvent): boolean {
|
|
113
|
+
return !e.dataTransfer?.types.includes(mime);
|
|
157
114
|
}
|
|
158
115
|
|
|
159
|
-
function
|
|
160
|
-
if (
|
|
161
|
-
|
|
116
|
+
function onDragEnter(e: DragEvent) {
|
|
117
|
+
if (ignore(e)) return;
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
if (depth++ === 0) element.classList.add('drag-over');
|
|
120
|
+
}
|
|
162
121
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
122
|
+
function onDragOver(e: DragEvent) {
|
|
123
|
+
if (ignore(e)) return;
|
|
124
|
+
// Required for the element to be a valid drop target.
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
|
166
127
|
}
|
|
167
128
|
|
|
168
|
-
function
|
|
169
|
-
if (e
|
|
170
|
-
|
|
171
|
-
|
|
129
|
+
function onDragLeave(e: DragEvent) {
|
|
130
|
+
if (ignore(e)) return;
|
|
131
|
+
if (--depth <= 0) {
|
|
132
|
+
depth = 0;
|
|
133
|
+
element.classList.remove('drag-over');
|
|
172
134
|
}
|
|
173
|
-
|
|
174
|
-
lastY = e.clientY;
|
|
175
|
-
started = false;
|
|
176
|
-
pointerId = e.pointerId;
|
|
135
|
+
}
|
|
177
136
|
|
|
178
|
-
|
|
179
|
-
if (e
|
|
137
|
+
function onDropEvent(e: DragEvent) {
|
|
138
|
+
if (ignore(e)) return;
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
depth = 0;
|
|
141
|
+
element.classList.remove('drag-over');
|
|
142
|
+
|
|
143
|
+
let items: T[];
|
|
144
|
+
try {
|
|
145
|
+
items = JSON.parse(e.dataTransfer!.getData(mime));
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!Array.isArray(items)) return;
|
|
150
|
+
const index = exclude ? items.indexOf(exclude) : -1;
|
|
151
|
+
if (index !== -1) items.splice(index, 1);
|
|
152
|
+
if (items.length) onDrop(items);
|
|
180
153
|
}
|
|
181
154
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (holdTimer !== undefined) return _clearTimer();
|
|
155
|
+
element.addEventListener('dragenter', onDragEnter);
|
|
156
|
+
element.addEventListener('dragover', onDragOver);
|
|
157
|
+
element.addEventListener('dragleave', onDragLeave);
|
|
158
|
+
element.addEventListener('drop', onDropEvent);
|
|
187
159
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
160
|
+
return () => {
|
|
161
|
+
element.removeEventListener('dragenter', onDragEnter);
|
|
162
|
+
element.removeEventListener('dragover', onDragOver);
|
|
163
|
+
element.removeEventListener('dragleave', onDragLeave);
|
|
164
|
+
element.removeEventListener('drop', onDropEvent);
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function notUpload(e: DragEvent): boolean {
|
|
170
|
+
return !e.dataTransfer?.types.includes('Files');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Register an element as a target for files dragged in from the system file manager.
|
|
175
|
+
*/
|
|
176
|
+
export function uploadTarget(label: string, onDrop: (entries: FileSystemEntry[]) => unknown): Attachment<HTMLElement> {
|
|
177
|
+
return function _attachUploadTarget(element: HTMLElement) {
|
|
178
|
+
let depth = 0,
|
|
179
|
+
indicator: HTMLElement | undefined,
|
|
180
|
+
icon: {} | undefined;
|
|
181
|
+
|
|
182
|
+
function hide() {
|
|
183
|
+
depth = 0;
|
|
184
|
+
element.classList.remove('drag-over');
|
|
185
|
+
if (icon) unmount(icon);
|
|
186
|
+
indicator?.remove();
|
|
191
187
|
}
|
|
192
188
|
|
|
193
|
-
function
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
lastY = e.clientY;
|
|
189
|
+
function onDragEnter(e: DragEvent) {
|
|
190
|
+
if (notUpload(e)) return;
|
|
191
|
+
// Stop drag events from bubbling so the innermost upload target wins
|
|
197
192
|
e.preventDefault();
|
|
198
|
-
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
if (depth++ !== 0) return;
|
|
195
|
+
element.classList.add('drag-over');
|
|
196
|
+
|
|
197
|
+
indicator = document.createElement('div');
|
|
198
|
+
indicator.className = 'drag-upload-indicator';
|
|
199
|
+
icon = mount(Icon, { target: indicator, props: { i: 'upload' } });
|
|
200
|
+
|
|
201
|
+
const text = document.createElement('span');
|
|
202
|
+
text.textContent = label;
|
|
203
|
+
indicator.appendChild(text);
|
|
204
|
+
|
|
205
|
+
document.body.appendChild(indicator);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
function
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
e.preventDefault();
|
|
207
|
-
e.stopPropagation();
|
|
208
|
-
suppressClick = true;
|
|
209
|
-
controller.end();
|
|
210
|
-
}
|
|
211
|
-
started = false;
|
|
208
|
+
function onDragOver(e: DragEvent) {
|
|
209
|
+
if (notUpload(e)) return;
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
function onDragLeave(e: DragEvent) {
|
|
216
|
+
if (notUpload(e)) return;
|
|
217
|
+
e.stopPropagation();
|
|
218
|
+
if (--depth <= 0) hide();
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
function onClick(e: MouseEvent) {
|
|
223
|
-
if (!suppressClick) return;
|
|
224
|
-
suppressClick = false;
|
|
221
|
+
function onDropEvent(e: DragEvent) {
|
|
222
|
+
if (notUpload(e)) return;
|
|
225
223
|
e.preventDefault();
|
|
226
224
|
e.stopPropagation();
|
|
225
|
+
hide();
|
|
226
|
+
|
|
227
|
+
const entries: FileSystemEntry[] = [];
|
|
228
|
+
for (const item of e.dataTransfer!.items) {
|
|
229
|
+
const entry = item.webkitGetAsEntry();
|
|
230
|
+
if (entry) entries.push(entry);
|
|
231
|
+
}
|
|
232
|
+
if (entries.length) onDrop(entries);
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
element.addEventListener('
|
|
230
|
-
element.addEventListener('
|
|
231
|
-
element.addEventListener('
|
|
232
|
-
element.addEventListener('
|
|
233
|
-
element.addEventListener('pointercancel', onPointerCancel);
|
|
234
|
-
element.addEventListener('click', onClick, { capture: true });
|
|
235
|
+
element.addEventListener('dragenter', onDragEnter);
|
|
236
|
+
element.addEventListener('dragover', onDragOver);
|
|
237
|
+
element.addEventListener('dragleave', onDragLeave);
|
|
238
|
+
element.addEventListener('drop', onDropEvent);
|
|
235
239
|
|
|
236
240
|
return () => {
|
|
237
|
-
|
|
238
|
-
element.removeEventListener('
|
|
239
|
-
element.removeEventListener('
|
|
240
|
-
element.removeEventListener('
|
|
241
|
-
element.removeEventListener('
|
|
242
|
-
element.removeEventListener('click', onClick, { capture: true });
|
|
241
|
+
hide();
|
|
242
|
+
element.removeEventListener('dragenter', onDragEnter);
|
|
243
|
+
element.removeEventListener('dragover', onDragOver);
|
|
244
|
+
element.removeEventListener('dragleave', onDragLeave);
|
|
245
|
+
element.removeEventListener('drop', onDropEvent);
|
|
243
246
|
};
|
|
244
247
|
};
|
|
245
248
|
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Register an element as a drop target for a {@link DragController}.
|
|
249
|
-
*
|
|
250
|
-
* When grabbed items are released over the element, `onDrop` is called with the dragged ids. While a
|
|
251
|
-
* drag hovers the element it carries the `drag-over` class.
|
|
252
|
-
*/
|
|
253
|
-
export function target(onDrop: (ids: string[]) => unknown, controller: Controller = defaultController): Attachment<HTMLElement> {
|
|
254
|
-
return function _attachDropTarget(element: HTMLElement) {
|
|
255
|
-
return controller._registerTarget(element, onDrop);
|
|
256
|
-
};
|
|
257
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
"./toast": "./lib/toast.js",
|
|
41
41
|
"./attachments": "./lib/attachments/index.js",
|
|
42
42
|
"./reactive": "./lib/reactive/index.svelte.js",
|
|
43
|
-
"./styles/*":
|
|
43
|
+
"./styles/*": {
|
|
44
|
+
"types": "./styles/styles.d.ts",
|
|
45
|
+
"default": "./styles/*.css"
|
|
46
|
+
}
|
|
44
47
|
},
|
|
45
48
|
"scripts": {
|
|
46
49
|
"build": "tsc"
|
package/styles/list.css
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '@axium/client/styles/*';
|