@oscarpalmer/oui 0.20.0 → 0.22.0
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/css/popover.css +9 -9
- package/dist/css/tooltip.css +20 -20
- package/dist/js/focus-trap/embedded.mjs +1 -1
- package/dist/js/focus-trap/standalone.mjs +1 -1
- package/dist/js/index.d.mts +47 -4
- package/dist/js/index.mjs +2537 -5
- package/dist/js/{attributable.d.mts → internal/attributable.d.mts} +1 -1
- package/dist/js/{attributable.mjs → internal/attributable.mjs} +1 -1
- package/dist/js/internal/draggable.d.mts +2 -0
- package/dist/js/internal/draggable.mjs +215 -0
- package/dist/js/{floatable.d.mts → internal/floatable.d.mts} +3 -3
- package/dist/js/internal/floatable.mjs +187 -0
- package/dist/js/movable-BKFgoaOl.d.mts +101 -0
- package/dist/js/movable.d.mts +2 -0
- package/dist/js/movable.mjs +152 -0
- package/dist/js/popover.mjs +2 -2
- package/dist/js/sortable.d.mts +2 -0
- package/dist/js/sortable.mjs +243 -0
- package/dist/js/splitter.mjs +4 -4
- package/dist/js/tooltip.d.mts +2 -2
- package/dist/js/tooltip.mjs +98 -100
- package/package.json +28 -18
- package/src/css/mixins.scss +8 -12
- package/src/css/popover.scss +1 -1
- package/src/css/tooltip.scss +22 -22
- package/src/js/focus-trap/standalone.ts +1 -1
- package/src/js/index.ts +3 -1
- package/src/js/internal/draggable.ts +515 -0
- package/src/js/internal/floatable.ts +373 -0
- package/src/js/movable.ts +260 -0
- package/src/js/popover.ts +2 -2
- package/src/js/sortable.ts +467 -0
- package/src/js/splitter.ts +1 -4
- package/src/js/tooltip.ts +132 -132
- package/dist/js/floatable.mjs +0 -284
- package/src/js/floatable.ts +0 -590
- /package/src/js/{attributable.ts → internal/attributable.ts} +0 -0
package/src/css/mixins.scss
CHANGED
|
@@ -60,24 +60,20 @@
|
|
|
60
60
|
margin: var(--margin-top, 0) 0 0 var(--margin-left, 0);
|
|
61
61
|
pointer-events: all !important;
|
|
62
62
|
|
|
63
|
-
&[position^='above']
|
|
64
|
-
|
|
65
|
-
--margin-top: -0.5rem;
|
|
63
|
+
&[oui-position^='above'] {
|
|
64
|
+
margin: 0 0 0.5em;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
&[position^='below']
|
|
69
|
-
|
|
70
|
-
--margin-top: 0.5rem;
|
|
67
|
+
&[oui-position^='below'] {
|
|
68
|
+
margin: 0.5em 0 0;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
&[position^='end']
|
|
74
|
-
|
|
75
|
-
--margin-left: 0.5rem;
|
|
71
|
+
&[oui-position^='end'] {
|
|
72
|
+
margin: 0 0 0 0.5em;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
&[position^='
|
|
79
|
-
|
|
80
|
-
--margin-left: -0.5rem;
|
|
75
|
+
&[oui-position^='start'] {
|
|
76
|
+
margin: 0 0.5em 0 0;
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
79
|
|
package/src/css/popover.scss
CHANGED
package/src/css/tooltip.scss
CHANGED
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
background-color: var(--oui-absolute);
|
|
38
38
|
border-radius: 0.25rem;
|
|
39
39
|
font-size: 0.875rem;
|
|
40
|
+
pointer-events: none;
|
|
40
41
|
transition: opacity 0.125s ease-in-out;
|
|
41
|
-
transition-delay: 0s;
|
|
42
42
|
|
|
43
43
|
&::after {
|
|
44
44
|
content: '';
|
|
@@ -46,49 +46,49 @@
|
|
|
46
46
|
position: absolute;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
&[position^='above'],
|
|
50
|
-
&[position^='below'],
|
|
51
|
-
&[position^='bottom'],
|
|
52
|
-
&[position^='top'] {
|
|
49
|
+
&[oui-position^='above'],
|
|
50
|
+
&[oui-position^='below'],
|
|
51
|
+
&[oui-position^='bottom'],
|
|
52
|
+
&[oui-position^='top'] {
|
|
53
53
|
&::after {
|
|
54
54
|
width: 100%;
|
|
55
|
-
height: 0.
|
|
55
|
+
height: 0.5em;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
&[position^='above'],
|
|
60
|
-
&[position^='top'] {
|
|
59
|
+
&[oui-position^='above'],
|
|
60
|
+
&[oui-position^='top'] {
|
|
61
61
|
&::after {
|
|
62
62
|
inset: 100% 0 0;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
&[position^='below'],
|
|
67
|
-
&[position^='bottom'] {
|
|
66
|
+
&[oui-position^='below'],
|
|
67
|
+
&[oui-position^='bottom'] {
|
|
68
68
|
&::after {
|
|
69
|
-
inset: -
|
|
69
|
+
inset: -0.5em 0 0;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
&[position^='end'],
|
|
74
|
-
&[position^='left'],
|
|
75
|
-
&[position^='right'],
|
|
76
|
-
&[position^='start'] {
|
|
73
|
+
&[oui-position^='end'],
|
|
74
|
+
&[oui-position^='left'],
|
|
75
|
+
&[oui-position^='right'],
|
|
76
|
+
&[oui-position^='start'] {
|
|
77
77
|
&::after {
|
|
78
|
-
width: 0.
|
|
78
|
+
width: 0.5em;
|
|
79
79
|
height: 100%;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
&[position^='end'],
|
|
84
|
-
&[position^='right'] {
|
|
83
|
+
&[oui-position^='end'],
|
|
84
|
+
&[oui-position^='right'] {
|
|
85
85
|
&::after {
|
|
86
|
-
inset: 0 0 0 -0.
|
|
86
|
+
inset: 0 0 0 -0.5em;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
&[position^='left'],
|
|
91
|
-
&[position^='start'] {
|
|
90
|
+
&[oui-position^='left'],
|
|
91
|
+
&[oui-position^='start'] {
|
|
92
92
|
&::after {
|
|
93
93
|
inset: 0 0 0 100%;
|
|
94
94
|
}
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
|
|
98
98
|
:where([oui-tooltip-content][oui-tooltip-content-active]) {
|
|
99
99
|
opacity: 1;
|
|
100
|
-
|
|
100
|
+
pointer-events: auto;
|
|
101
101
|
}
|
package/src/js/index.ts
CHANGED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {clamp} from '@oscarpalmer/atoms/number';
|
|
3
|
+
import {getPosition, on} from '@oscarpalmer/toretto/event';
|
|
4
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
|
+
import {toggleStyles} from '@oscarpalmer/toretto/style';
|
|
6
|
+
import supportsTouch from '@oscarpalmer/toretto/touch';
|
|
7
|
+
import type {Movable} from '../movable';
|
|
8
|
+
import type {Sortable} from '../sortable';
|
|
9
|
+
|
|
10
|
+
// #region Types
|
|
11
|
+
|
|
12
|
+
type Direction = 'horizontal' | 'vertical' | 'x' | 'y';
|
|
13
|
+
|
|
14
|
+
export abstract class Draggable {
|
|
15
|
+
container: HTMLElement | undefined;
|
|
16
|
+
|
|
17
|
+
hasHandles: boolean | undefined;
|
|
18
|
+
|
|
19
|
+
horizontal = true;
|
|
20
|
+
|
|
21
|
+
vertical = true;
|
|
22
|
+
|
|
23
|
+
abstract readonly dragged: HTMLElement;
|
|
24
|
+
|
|
25
|
+
abstract origin: HTMLElement | undefined;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
public prefix: string,
|
|
29
|
+
public element: HTMLElement,
|
|
30
|
+
public options: DraggableOptions,
|
|
31
|
+
) {
|
|
32
|
+
setContainer(this, options);
|
|
33
|
+
setDirection(this, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
destroy(): void {
|
|
37
|
+
unsetContainer(this, this.options);
|
|
38
|
+
|
|
39
|
+
this.container = undefined;
|
|
40
|
+
this.element = undefined!;
|
|
41
|
+
this.options = undefined!;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type DragBegin = (
|
|
46
|
+
event: MouseEvent | TouchEvent,
|
|
47
|
+
state: DraggableState,
|
|
48
|
+
draggable: Draggable,
|
|
49
|
+
element: HTMLElement,
|
|
50
|
+
handle: HTMLElement,
|
|
51
|
+
position: EventPosition,
|
|
52
|
+
) => void;
|
|
53
|
+
|
|
54
|
+
type DragCancel = (state: DraggableState, draggable: Draggable) => void;
|
|
55
|
+
|
|
56
|
+
type DragEnd = (
|
|
57
|
+
event: MouseEvent | TouchEvent,
|
|
58
|
+
state: DraggableState,
|
|
59
|
+
draggable: Draggable,
|
|
60
|
+
reset: () => void,
|
|
61
|
+
) => void;
|
|
62
|
+
|
|
63
|
+
type DragMove = (
|
|
64
|
+
event: MouseEvent | TouchEvent,
|
|
65
|
+
state: DraggableState,
|
|
66
|
+
draggable: Draggable,
|
|
67
|
+
position: DragMovePosition,
|
|
68
|
+
) => PlainObject | undefined;
|
|
69
|
+
|
|
70
|
+
export type DragMovePosition = {
|
|
71
|
+
calculated: EventPosition;
|
|
72
|
+
original: EventPosition;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type DraggableBound<
|
|
76
|
+
Property extends keyof DraggableInstances = keyof DraggableInstances,
|
|
77
|
+
Instance extends DraggableInstances[Property] = DraggableInstances[Property],
|
|
78
|
+
> = {
|
|
79
|
+
constructor: new (element: HTMLElement) => Instance;
|
|
80
|
+
property: Property;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type DraggableInstances = {
|
|
84
|
+
movable?: Movable;
|
|
85
|
+
sortable?: Sortable;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type DraggableOptions = {
|
|
89
|
+
container: DraggableOptionsContainer;
|
|
90
|
+
direction: DraggableOptionsDirection;
|
|
91
|
+
drag: DraggableOptionsDrag;
|
|
92
|
+
position: DraggableOptionsPosition;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type DraggableOptionsContainer = {
|
|
96
|
+
attribute: string;
|
|
97
|
+
map: Map<HTMLElement, Set<Draggable>>;
|
|
98
|
+
onDestroy?: DraggableOptionsContainerOnDestroy;
|
|
99
|
+
onInitialize?: DraggableOptionsContainerOnInitialize;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type DraggableOptionsContainerOnDestroy = (
|
|
103
|
+
draggable: Draggable,
|
|
104
|
+
container: HTMLElement,
|
|
105
|
+
empty: boolean,
|
|
106
|
+
) => void;
|
|
107
|
+
|
|
108
|
+
type DraggableOptionsContainerOnInitialize = (draggable: Draggable) => void;
|
|
109
|
+
|
|
110
|
+
type DraggableOptionsDirection = {
|
|
111
|
+
attribute: string;
|
|
112
|
+
onAfter?: DraggableOptionsDirectionOnAfter;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type DraggableOptionsDirectionOnAfter = (draggable: Draggable) => void;
|
|
116
|
+
|
|
117
|
+
type DraggableOptionsDrag = {
|
|
118
|
+
onBegin: DragBegin;
|
|
119
|
+
onCancel: DragCancel;
|
|
120
|
+
onEnd: DragEnd;
|
|
121
|
+
onMove: DragMove;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type DraggableOptionsPosition = {
|
|
125
|
+
getOffset: DraggableOptionsPositionGetOffset;
|
|
126
|
+
getOriginal: DraggableOptionsPositionGetOriginal;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
type DraggableOptionsPositionGetOffset = (
|
|
130
|
+
state: DraggableState,
|
|
131
|
+
draggable: Draggable,
|
|
132
|
+
element: HTMLElement,
|
|
133
|
+
position: EventPosition,
|
|
134
|
+
) => EventPosition;
|
|
135
|
+
|
|
136
|
+
type DraggableOptionsPositionGetOriginal = (
|
|
137
|
+
state: DraggableState,
|
|
138
|
+
draggable: Draggable,
|
|
139
|
+
position: EventPosition,
|
|
140
|
+
) => EventPosition;
|
|
141
|
+
|
|
142
|
+
export type DraggableState = {
|
|
143
|
+
container: DOMRect | undefined;
|
|
144
|
+
element: DraggableStateElement | undefined;
|
|
145
|
+
instances: WeakMap<HTMLElement, DraggableInstances>;
|
|
146
|
+
offset: EventPosition | undefined;
|
|
147
|
+
original: EventPosition | undefined;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type DraggableStateElement = {
|
|
151
|
+
node: HTMLElement;
|
|
152
|
+
rectangle: DOMRect;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// #endregion
|
|
156
|
+
|
|
157
|
+
// #region Functions
|
|
158
|
+
|
|
159
|
+
export function addDraggable<
|
|
160
|
+
Property extends keyof DraggableInstances,
|
|
161
|
+
Instance extends DraggableInstances[Property],
|
|
162
|
+
>(this: DraggableBound<Property, Instance>, element: HTMLElement): void {
|
|
163
|
+
let instances = state.instances.get(element);
|
|
164
|
+
|
|
165
|
+
if (instances == null) {
|
|
166
|
+
instances = {};
|
|
167
|
+
|
|
168
|
+
state.instances.set(element, instances);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
instances[this.property] ??= new this.constructor(element);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getHandle(event: Event, isMovable: boolean): HTMLElement | null {
|
|
175
|
+
return findAncestor(
|
|
176
|
+
event,
|
|
177
|
+
isMovable ? SELECTOR_MOVABLE_HANDLE : SELECTOR_SORTABLE_HANDLE,
|
|
178
|
+
) as HTMLElement;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getNextPosition(draggable: Draggable, position: EventPosition): EventPosition {
|
|
182
|
+
let x = state.original?.x ?? 0;
|
|
183
|
+
let y = state.original?.y ?? 0;
|
|
184
|
+
|
|
185
|
+
if (draggable.horizontal) {
|
|
186
|
+
x = position.x - (state.offset?.x ?? 0);
|
|
187
|
+
|
|
188
|
+
if (state.container != null && state.element != null) {
|
|
189
|
+
x = clamp(x, state.container.left, state.container.right - state.element.rectangle.width);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (draggable.vertical) {
|
|
194
|
+
y = position.y - (state.offset?.y ?? 0);
|
|
195
|
+
|
|
196
|
+
if (state.container != null && state.element != null) {
|
|
197
|
+
y = clamp(y, state.container.top, state.container.bottom - state.element.rectangle.height);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {x, y};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
205
|
+
if (current == null || event.key !== KEY_ESCAPE) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const {origin} = current;
|
|
210
|
+
|
|
211
|
+
current.options.drag.onCancel?.(state, current);
|
|
212
|
+
|
|
213
|
+
current.element.dispatchEvent(
|
|
214
|
+
new CustomEvent(`${current.prefix}:cancel`, {
|
|
215
|
+
detail: {
|
|
216
|
+
element: origin,
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
reset();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
225
|
+
if (
|
|
226
|
+
event.altKey ||
|
|
227
|
+
event.ctrlKey ||
|
|
228
|
+
event.metaKey ||
|
|
229
|
+
event.shiftKey ||
|
|
230
|
+
(supportsTouch.value
|
|
231
|
+
? event.type === 'mousedown'
|
|
232
|
+
: event.type === 'touchstart' || (event as MouseEvent).button !== 0)
|
|
233
|
+
) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const container = findAncestor(event, SELECTOR)! as HTMLElement;
|
|
238
|
+
const instances = state.instances.get(container!);
|
|
239
|
+
|
|
240
|
+
if (instances == null) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const isMovable = container.hasAttribute(ATTRIBUTE_MOVABLE);
|
|
245
|
+
|
|
246
|
+
const instance = isMovable ? instances.movable : instances.sortable;
|
|
247
|
+
|
|
248
|
+
if (instance == null) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (supportsTouch.value) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let element: HTMLElement | null = container;
|
|
257
|
+
|
|
258
|
+
if (!isMovable) {
|
|
259
|
+
element = findAncestor(event, SELECTOR_SORTABLE_ITEM) as HTMLElement;
|
|
260
|
+
|
|
261
|
+
if (element == null || element.parentElement !== container) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let handle: HTMLElement | null = element;
|
|
267
|
+
|
|
268
|
+
instance.hasHandles ??= element.querySelector(SELECTOR_SORTABLE_HANDLE) != null;
|
|
269
|
+
|
|
270
|
+
if (instance.hasHandles) {
|
|
271
|
+
handle = getHandle(event, isMovable);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (handle == null) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const {dragged} = instance;
|
|
279
|
+
|
|
280
|
+
const rectangle = dragged.getBoundingClientRect();
|
|
281
|
+
|
|
282
|
+
const position = getPosition(event) ?? {x: 0, y: 0};
|
|
283
|
+
|
|
284
|
+
current = instance;
|
|
285
|
+
|
|
286
|
+
state.container = instance.container?.getBoundingClientRect();
|
|
287
|
+
|
|
288
|
+
state.element = {
|
|
289
|
+
rectangle,
|
|
290
|
+
node: dragged,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
state.offset = instance.options.position.getOffset(state, instance, element, position);
|
|
294
|
+
state.original = instance.options.position.getOriginal(state, instance, position);
|
|
295
|
+
|
|
296
|
+
const begin = new CustomEvent(`${instance.prefix}:begin`, {
|
|
297
|
+
detail: {
|
|
298
|
+
element,
|
|
299
|
+
position: {...state.original},
|
|
300
|
+
},
|
|
301
|
+
cancelable: true,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
instance.element.dispatchEvent(begin);
|
|
305
|
+
|
|
306
|
+
setTimeout(() => {
|
|
307
|
+
if (begin.defaultPrevented) {
|
|
308
|
+
current?.options.drag.onCancel(state, instance);
|
|
309
|
+
|
|
310
|
+
reset();
|
|
311
|
+
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const next = getNextPosition(instance, position);
|
|
316
|
+
|
|
317
|
+
dragged.style.position = 'fixed';
|
|
318
|
+
dragged.style.inset = `${next.y}px auto auto ${next.x}px`;
|
|
319
|
+
|
|
320
|
+
instance.options.drag.onBegin(event, state, instance, element, handle, next);
|
|
321
|
+
|
|
322
|
+
styleToggler.set();
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
327
|
+
if (current == null) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const position = getPosition(event);
|
|
332
|
+
|
|
333
|
+
if (position == null) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const next = getNextPosition(current, position);
|
|
338
|
+
|
|
339
|
+
if (state.element != null) {
|
|
340
|
+
state.element.node.style.inset = `${next.y}px auto auto ${next.x}px`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const detail = current.options.drag.onMove(event, state, current, {
|
|
344
|
+
calculated: next,
|
|
345
|
+
original: position,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
current.element.dispatchEvent(
|
|
349
|
+
new CustomEvent(`${current.prefix}:move`, {
|
|
350
|
+
detail,
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function onPointerup(event: MouseEvent | TouchEvent): void {
|
|
356
|
+
current?.options.drag.onEnd?.(event, state, current, reset);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function removeDraggable<
|
|
360
|
+
Property extends keyof DraggableInstances,
|
|
361
|
+
Instance extends DraggableInstances[Property],
|
|
362
|
+
>(
|
|
363
|
+
this: {
|
|
364
|
+
constructor: new (element: HTMLElement) => Instance;
|
|
365
|
+
property: Property;
|
|
366
|
+
},
|
|
367
|
+
element: HTMLElement,
|
|
368
|
+
): void {
|
|
369
|
+
const {property} = this;
|
|
370
|
+
|
|
371
|
+
const instances = state.instances.get(element);
|
|
372
|
+
|
|
373
|
+
if (instances == null || instances[property] == null) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
instances[property]?.destroy();
|
|
378
|
+
|
|
379
|
+
instances[property] = undefined;
|
|
380
|
+
|
|
381
|
+
if (instances[properties[property]] == null) {
|
|
382
|
+
state.instances.delete(element);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function reset(): void {
|
|
387
|
+
current = undefined;
|
|
388
|
+
|
|
389
|
+
state.container = undefined;
|
|
390
|
+
state.element = undefined;
|
|
391
|
+
state.offset = undefined;
|
|
392
|
+
state.original = undefined;
|
|
393
|
+
|
|
394
|
+
styleToggler.remove();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function setContainer(draggable: Draggable, options: DraggableOptions): void {
|
|
398
|
+
const selector = draggable.element.getAttribute(options.container.attribute);
|
|
399
|
+
|
|
400
|
+
if (typeof selector !== 'string') {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
draggable.container = globals.has(selector)
|
|
405
|
+
? draggable.element.ownerDocument.body
|
|
406
|
+
: (document.querySelector(selector) ?? undefined);
|
|
407
|
+
|
|
408
|
+
if (draggable.container == null) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const set = options.container.map.get(draggable.container);
|
|
413
|
+
|
|
414
|
+
if (set == null) {
|
|
415
|
+
options.container.map.set(draggable.container, new Set([draggable]));
|
|
416
|
+
} else {
|
|
417
|
+
set.add(draggable);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
options.container.onInitialize?.(draggable);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function setDirection(draggable: Draggable, options: DraggableOptions): void {
|
|
424
|
+
const direction = draggable.element.getAttribute(options.direction.attribute);
|
|
425
|
+
|
|
426
|
+
if (horizontals.has(direction as Direction)) {
|
|
427
|
+
draggable.vertical = false;
|
|
428
|
+
} else if (verticals.has(direction as Direction)) {
|
|
429
|
+
draggable.horizontal = false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
options.direction.onAfter?.(draggable);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function unsetContainer(draggable: Draggable, options: DraggableOptions): void {
|
|
436
|
+
const {container} = draggable;
|
|
437
|
+
|
|
438
|
+
if (container == null) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const set = options.container.map.get(container);
|
|
443
|
+
|
|
444
|
+
let empty = false;
|
|
445
|
+
|
|
446
|
+
if (set != null) {
|
|
447
|
+
set.delete(draggable);
|
|
448
|
+
|
|
449
|
+
if (set.size === 0) {
|
|
450
|
+
empty = true;
|
|
451
|
+
|
|
452
|
+
options.container.map.delete(container);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
options.container.onDestroy?.(draggable, container, empty);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// #endregion
|
|
460
|
+
|
|
461
|
+
// #region Variables
|
|
462
|
+
|
|
463
|
+
const ATTRIBUTE_MOVABLE = 'oui-movable';
|
|
464
|
+
|
|
465
|
+
const ATTRIBUTE_SORTABLE = 'oui-sortable';
|
|
466
|
+
|
|
467
|
+
const KEY_ESCAPE = 'Escape';
|
|
468
|
+
|
|
469
|
+
const SELECTOR = `[${ATTRIBUTE_MOVABLE}], [${ATTRIBUTE_SORTABLE}]`;
|
|
470
|
+
|
|
471
|
+
const SELECTOR_MOVABLE_HANDLE = `[${ATTRIBUTE_MOVABLE}-handle]`;
|
|
472
|
+
|
|
473
|
+
const SELECTOR_SORTABLE_HANDLE = `[${ATTRIBUTE_SORTABLE}-handle]`;
|
|
474
|
+
|
|
475
|
+
const SELECTOR_SORTABLE_ITEM = `[${ATTRIBUTE_SORTABLE}] > *`;
|
|
476
|
+
|
|
477
|
+
const globals = new Set<string>(['body', 'document', 'window']);
|
|
478
|
+
|
|
479
|
+
const horizontals = new Set<Direction>(['horizontal', 'x']);
|
|
480
|
+
|
|
481
|
+
const properties: Record<keyof DraggableInstances, keyof DraggableInstances> = {
|
|
482
|
+
movable: 'sortable',
|
|
483
|
+
sortable: 'movable',
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const state: DraggableState = {
|
|
487
|
+
container: undefined,
|
|
488
|
+
element: undefined,
|
|
489
|
+
instances: new WeakMap(),
|
|
490
|
+
offset: undefined,
|
|
491
|
+
original: undefined,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const styleToggler = toggleStyles(document.body, {
|
|
495
|
+
touchAction: 'none',
|
|
496
|
+
userSelect: 'none',
|
|
497
|
+
webkitUserSelect: 'none',
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const verticals = new Set<Direction>(['vertical', 'y']);
|
|
501
|
+
|
|
502
|
+
let current: Movable | Sortable | undefined;
|
|
503
|
+
|
|
504
|
+
// #endregion
|
|
505
|
+
|
|
506
|
+
// #region Initialization
|
|
507
|
+
|
|
508
|
+
on(document, 'keydown', onKeydown);
|
|
509
|
+
on(document, 'mousedown', onPointerdown);
|
|
510
|
+
on(document, 'pointermove', onPointermove);
|
|
511
|
+
on(document, 'pointerup', onPointerup);
|
|
512
|
+
on(document, 'touchcancel', onPointerup, {passive: false});
|
|
513
|
+
on(document, 'touchstart', onPointerdown, {passive: false});
|
|
514
|
+
|
|
515
|
+
// #endregion
|