@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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { clamp } from "@oscarpalmer/atoms/number";
|
|
2
|
+
import { getPosition, on } from "@oscarpalmer/toretto/event";
|
|
3
|
+
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
4
|
+
import { toggleStyles } from "@oscarpalmer/toretto/style";
|
|
5
|
+
import supportsTouch from "@oscarpalmer/toretto/touch";
|
|
6
|
+
//#region src/js/internal/draggable.ts
|
|
7
|
+
var Draggable = class {
|
|
8
|
+
prefix;
|
|
9
|
+
element;
|
|
10
|
+
options;
|
|
11
|
+
container;
|
|
12
|
+
hasHandles;
|
|
13
|
+
horizontal = true;
|
|
14
|
+
vertical = true;
|
|
15
|
+
constructor(prefix, element, options) {
|
|
16
|
+
this.prefix = prefix;
|
|
17
|
+
this.element = element;
|
|
18
|
+
this.options = options;
|
|
19
|
+
setContainer(this, options);
|
|
20
|
+
setDirection(this, options);
|
|
21
|
+
}
|
|
22
|
+
destroy() {
|
|
23
|
+
unsetContainer(this, this.options);
|
|
24
|
+
this.container = void 0;
|
|
25
|
+
this.element = void 0;
|
|
26
|
+
this.options = void 0;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function addDraggable(element) {
|
|
30
|
+
let instances = state.instances.get(element);
|
|
31
|
+
if (instances == null) {
|
|
32
|
+
instances = {};
|
|
33
|
+
state.instances.set(element, instances);
|
|
34
|
+
}
|
|
35
|
+
instances[this.property] ??= new this.constructor(element);
|
|
36
|
+
}
|
|
37
|
+
function getHandle(event, isMovable) {
|
|
38
|
+
return findAncestor(event, isMovable ? SELECTOR_MOVABLE_HANDLE : SELECTOR_SORTABLE_HANDLE);
|
|
39
|
+
}
|
|
40
|
+
function getNextPosition(draggable, position) {
|
|
41
|
+
let x = state.original?.x ?? 0;
|
|
42
|
+
let y = state.original?.y ?? 0;
|
|
43
|
+
if (draggable.horizontal) {
|
|
44
|
+
x = position.x - (state.offset?.x ?? 0);
|
|
45
|
+
if (state.container != null && state.element != null) x = clamp(x, state.container.left, state.container.right - state.element.rectangle.width);
|
|
46
|
+
}
|
|
47
|
+
if (draggable.vertical) {
|
|
48
|
+
y = position.y - (state.offset?.y ?? 0);
|
|
49
|
+
if (state.container != null && state.element != null) y = clamp(y, state.container.top, state.container.bottom - state.element.rectangle.height);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
x,
|
|
53
|
+
y
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function onKeydown(event) {
|
|
57
|
+
if (current == null || event.key !== KEY_ESCAPE) return;
|
|
58
|
+
const { origin } = current;
|
|
59
|
+
current.options.drag.onCancel?.(state, current);
|
|
60
|
+
current.element.dispatchEvent(new CustomEvent(`${current.prefix}:cancel`, { detail: { element: origin } }));
|
|
61
|
+
reset();
|
|
62
|
+
}
|
|
63
|
+
function onPointerdown(event) {
|
|
64
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || (supportsTouch.value ? event.type === "mousedown" : event.type === "touchstart" || event.button !== 0)) return;
|
|
65
|
+
const container = findAncestor(event, SELECTOR);
|
|
66
|
+
const instances = state.instances.get(container);
|
|
67
|
+
if (instances == null) return;
|
|
68
|
+
const isMovable = container.hasAttribute(ATTRIBUTE_MOVABLE);
|
|
69
|
+
const instance = isMovable ? instances.movable : instances.sortable;
|
|
70
|
+
if (instance == null) return;
|
|
71
|
+
if (supportsTouch.value) event.preventDefault();
|
|
72
|
+
let element = container;
|
|
73
|
+
if (!isMovable) {
|
|
74
|
+
element = findAncestor(event, SELECTOR_SORTABLE_ITEM);
|
|
75
|
+
if (element == null || element.parentElement !== container) return;
|
|
76
|
+
}
|
|
77
|
+
let handle = element;
|
|
78
|
+
instance.hasHandles ??= element.querySelector(SELECTOR_SORTABLE_HANDLE) != null;
|
|
79
|
+
if (instance.hasHandles) handle = getHandle(event, isMovable);
|
|
80
|
+
if (handle == null) return;
|
|
81
|
+
const { dragged } = instance;
|
|
82
|
+
const rectangle = dragged.getBoundingClientRect();
|
|
83
|
+
const position = getPosition(event) ?? {
|
|
84
|
+
x: 0,
|
|
85
|
+
y: 0
|
|
86
|
+
};
|
|
87
|
+
current = instance;
|
|
88
|
+
state.container = instance.container?.getBoundingClientRect();
|
|
89
|
+
state.element = {
|
|
90
|
+
rectangle,
|
|
91
|
+
node: dragged
|
|
92
|
+
};
|
|
93
|
+
state.offset = instance.options.position.getOffset(state, instance, element, position);
|
|
94
|
+
state.original = instance.options.position.getOriginal(state, instance, position);
|
|
95
|
+
const begin = new CustomEvent(`${instance.prefix}:begin`, {
|
|
96
|
+
detail: {
|
|
97
|
+
element,
|
|
98
|
+
position: { ...state.original }
|
|
99
|
+
},
|
|
100
|
+
cancelable: true
|
|
101
|
+
});
|
|
102
|
+
instance.element.dispatchEvent(begin);
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
if (begin.defaultPrevented) {
|
|
105
|
+
current?.options.drag.onCancel(state, instance);
|
|
106
|
+
reset();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const next = getNextPosition(instance, position);
|
|
110
|
+
dragged.style.position = "fixed";
|
|
111
|
+
dragged.style.inset = `${next.y}px auto auto ${next.x}px`;
|
|
112
|
+
instance.options.drag.onBegin(event, state, instance, element, handle, next);
|
|
113
|
+
styleToggler.set();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function onPointermove(event) {
|
|
117
|
+
if (current == null) return;
|
|
118
|
+
const position = getPosition(event);
|
|
119
|
+
if (position == null) return;
|
|
120
|
+
const next = getNextPosition(current, position);
|
|
121
|
+
if (state.element != null) state.element.node.style.inset = `${next.y}px auto auto ${next.x}px`;
|
|
122
|
+
const detail = current.options.drag.onMove(event, state, current, {
|
|
123
|
+
calculated: next,
|
|
124
|
+
original: position
|
|
125
|
+
});
|
|
126
|
+
current.element.dispatchEvent(new CustomEvent(`${current.prefix}:move`, { detail }));
|
|
127
|
+
}
|
|
128
|
+
function onPointerup(event) {
|
|
129
|
+
current?.options.drag.onEnd?.(event, state, current, reset);
|
|
130
|
+
}
|
|
131
|
+
function removeDraggable(element) {
|
|
132
|
+
const { property } = this;
|
|
133
|
+
const instances = state.instances.get(element);
|
|
134
|
+
if (instances == null || instances[property] == null) return;
|
|
135
|
+
instances[property]?.destroy();
|
|
136
|
+
instances[property] = void 0;
|
|
137
|
+
if (instances[properties[property]] == null) state.instances.delete(element);
|
|
138
|
+
}
|
|
139
|
+
function reset() {
|
|
140
|
+
current = void 0;
|
|
141
|
+
state.container = void 0;
|
|
142
|
+
state.element = void 0;
|
|
143
|
+
state.offset = void 0;
|
|
144
|
+
state.original = void 0;
|
|
145
|
+
styleToggler.remove();
|
|
146
|
+
}
|
|
147
|
+
function setContainer(draggable, options) {
|
|
148
|
+
const selector = draggable.element.getAttribute(options.container.attribute);
|
|
149
|
+
if (typeof selector !== "string") return;
|
|
150
|
+
draggable.container = globals.has(selector) ? draggable.element.ownerDocument.body : document.querySelector(selector) ?? void 0;
|
|
151
|
+
if (draggable.container == null) return;
|
|
152
|
+
const set = options.container.map.get(draggable.container);
|
|
153
|
+
if (set == null) options.container.map.set(draggable.container, new Set([draggable]));
|
|
154
|
+
else set.add(draggable);
|
|
155
|
+
options.container.onInitialize?.(draggable);
|
|
156
|
+
}
|
|
157
|
+
function setDirection(draggable, options) {
|
|
158
|
+
const direction = draggable.element.getAttribute(options.direction.attribute);
|
|
159
|
+
if (horizontals.has(direction)) draggable.vertical = false;
|
|
160
|
+
else if (verticals.has(direction)) draggable.horizontal = false;
|
|
161
|
+
options.direction.onAfter?.(draggable);
|
|
162
|
+
}
|
|
163
|
+
function unsetContainer(draggable, options) {
|
|
164
|
+
const { container } = draggable;
|
|
165
|
+
if (container == null) return;
|
|
166
|
+
const set = options.container.map.get(container);
|
|
167
|
+
let empty = false;
|
|
168
|
+
if (set != null) {
|
|
169
|
+
set.delete(draggable);
|
|
170
|
+
if (set.size === 0) {
|
|
171
|
+
empty = true;
|
|
172
|
+
options.container.map.delete(container);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
options.container.onDestroy?.(draggable, container, empty);
|
|
176
|
+
}
|
|
177
|
+
const ATTRIBUTE_MOVABLE = "oui-movable";
|
|
178
|
+
const ATTRIBUTE_SORTABLE = "oui-sortable";
|
|
179
|
+
const KEY_ESCAPE = "Escape";
|
|
180
|
+
const SELECTOR = `[${ATTRIBUTE_MOVABLE}], [${ATTRIBUTE_SORTABLE}]`;
|
|
181
|
+
const SELECTOR_MOVABLE_HANDLE = `[${ATTRIBUTE_MOVABLE}-handle]`;
|
|
182
|
+
const SELECTOR_SORTABLE_HANDLE = `[${ATTRIBUTE_SORTABLE}-handle]`;
|
|
183
|
+
const SELECTOR_SORTABLE_ITEM = `[${ATTRIBUTE_SORTABLE}] > *`;
|
|
184
|
+
const globals = new Set([
|
|
185
|
+
"body",
|
|
186
|
+
"document",
|
|
187
|
+
"window"
|
|
188
|
+
]);
|
|
189
|
+
const horizontals = new Set(["horizontal", "x"]);
|
|
190
|
+
const properties = {
|
|
191
|
+
movable: "sortable",
|
|
192
|
+
sortable: "movable"
|
|
193
|
+
};
|
|
194
|
+
const state = {
|
|
195
|
+
container: void 0,
|
|
196
|
+
element: void 0,
|
|
197
|
+
instances: /* @__PURE__ */ new WeakMap(),
|
|
198
|
+
offset: void 0,
|
|
199
|
+
original: void 0
|
|
200
|
+
};
|
|
201
|
+
const styleToggler = toggleStyles(document.body, {
|
|
202
|
+
touchAction: "none",
|
|
203
|
+
userSelect: "none",
|
|
204
|
+
webkitUserSelect: "none"
|
|
205
|
+
});
|
|
206
|
+
const verticals = new Set(["vertical", "y"]);
|
|
207
|
+
let current;
|
|
208
|
+
on(document, "keydown", onKeydown);
|
|
209
|
+
on(document, "mousedown", onPointerdown);
|
|
210
|
+
on(document, "pointermove", onPointermove);
|
|
211
|
+
on(document, "pointerup", onPointerup);
|
|
212
|
+
on(document, "touchcancel", onPointerup, { passive: false });
|
|
213
|
+
on(document, "touchstart", onPointerdown, { passive: false });
|
|
214
|
+
//#endregion
|
|
215
|
+
export { Draggable, addDraggable, removeDraggable };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/js/floatable.d.ts
|
|
1
|
+
//#region src/js/internal/floatable.d.ts
|
|
2
2
|
declare class Floatable {
|
|
3
3
|
readonly options: Options;
|
|
4
4
|
active: boolean;
|
|
@@ -17,10 +17,10 @@ type Options = {
|
|
|
17
17
|
defaultPosition: Position;
|
|
18
18
|
interactive: boolean;
|
|
19
19
|
positionAttribute: string;
|
|
20
|
-
|
|
20
|
+
reusable: boolean;
|
|
21
21
|
onAfter?: (active: boolean) => void;
|
|
22
22
|
};
|
|
23
|
-
type Position = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | '
|
|
23
|
+
type Position = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | 'start' | 'start-bottom' | 'start-top';
|
|
24
24
|
declare function deactivate(floatable: Floatable): void;
|
|
25
25
|
//#endregion
|
|
26
26
|
export { Floatable, deactivate };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { on } from "@oscarpalmer/toretto/event";
|
|
2
|
+
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
3
|
+
//#region src/js/internal/floatable.ts
|
|
4
|
+
var Floatable = class {
|
|
5
|
+
options;
|
|
6
|
+
active = false;
|
|
7
|
+
disabled = false;
|
|
8
|
+
frame;
|
|
9
|
+
ignoreFocus = false;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
initialize(this);
|
|
13
|
+
}
|
|
14
|
+
destroy() {
|
|
15
|
+
stop(this);
|
|
16
|
+
const state = getState();
|
|
17
|
+
state?.active.delete(this);
|
|
18
|
+
state?.mapped.delete(this.options.anchor);
|
|
19
|
+
state?.mapped.delete(this.options.content);
|
|
20
|
+
this.options.anchor = void 0;
|
|
21
|
+
this.options.content = void 0;
|
|
22
|
+
}
|
|
23
|
+
disable() {
|
|
24
|
+
if (this.disabled) return;
|
|
25
|
+
this.disabled = true;
|
|
26
|
+
stop(this);
|
|
27
|
+
getState()?.active.delete(this);
|
|
28
|
+
}
|
|
29
|
+
enable() {
|
|
30
|
+
if (!this.disabled) return;
|
|
31
|
+
this.disabled = false;
|
|
32
|
+
initialize(this);
|
|
33
|
+
}
|
|
34
|
+
toggle(active, ignoreFocus) {
|
|
35
|
+
if (this.disabled || this.active === active) return;
|
|
36
|
+
const state = getState();
|
|
37
|
+
if (state == null) return;
|
|
38
|
+
this.ignoreFocus = !active && (ignoreFocus ?? false);
|
|
39
|
+
stop(this);
|
|
40
|
+
if (active) state.active.add(this);
|
|
41
|
+
else state.active.delete(this);
|
|
42
|
+
if (this.options.interactive) if (active) state.order.push(this);
|
|
43
|
+
else {
|
|
44
|
+
const index = state.order.indexOf(this);
|
|
45
|
+
if (index > -1) state.order.splice(index, 1);
|
|
46
|
+
}
|
|
47
|
+
if (active) activate(this);
|
|
48
|
+
else deactivate(this);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function activate(floatable) {
|
|
52
|
+
floatable.active = true;
|
|
53
|
+
document.body.append(floatable.options.content);
|
|
54
|
+
floatable.options.content.hidden = false;
|
|
55
|
+
setPosition(floatable);
|
|
56
|
+
}
|
|
57
|
+
function closeAbove(element) {
|
|
58
|
+
const state = getState();
|
|
59
|
+
if (state == null) return;
|
|
60
|
+
const index = state.order.findIndex((floatable) => floatable.options.anchor === element || floatable.options.content === element);
|
|
61
|
+
if (index === -1 || index === state.order.length - 1) return;
|
|
62
|
+
while (index < state.order.length - 1) state.order.pop()?.toggle(false, true);
|
|
63
|
+
}
|
|
64
|
+
function closeAll() {
|
|
65
|
+
const state = getState();
|
|
66
|
+
const floatables = [...state?.active ?? []];
|
|
67
|
+
state?.active.clear();
|
|
68
|
+
for (const floatable of floatables) floatable.toggle(false, true);
|
|
69
|
+
}
|
|
70
|
+
function closeNonInteractive(state) {
|
|
71
|
+
for (const floatable of state.active) if (!floatable.options.interactive) floatable.toggle(false);
|
|
72
|
+
}
|
|
73
|
+
function deactivate(floatable) {
|
|
74
|
+
floatable.active = false;
|
|
75
|
+
if (!floatable.options.reusable) {
|
|
76
|
+
floatable.options.content.hidden = true;
|
|
77
|
+
floatable.options.anchor.insertAdjacentElement(INSERT_AFTEREND, floatable.options.content);
|
|
78
|
+
}
|
|
79
|
+
floatable.options.onAfter?.(false);
|
|
80
|
+
}
|
|
81
|
+
function getAnchorName() {
|
|
82
|
+
index += 1;
|
|
83
|
+
return `--oui-floatable-anchor-${index}`;
|
|
84
|
+
}
|
|
85
|
+
function getState() {
|
|
86
|
+
return window._oscarpalmer_oui_floatable;
|
|
87
|
+
}
|
|
88
|
+
function initialize(floatable) {
|
|
89
|
+
const { anchor, content, interactive } = floatable.options;
|
|
90
|
+
if (interactive) {
|
|
91
|
+
anchor.setAttribute(ATTRIBUTE_ANCHOR, "");
|
|
92
|
+
content.setAttribute(ATTRIBUTE_CONTENT, "");
|
|
93
|
+
}
|
|
94
|
+
const anchorName = getAnchorName();
|
|
95
|
+
anchor.style.anchorName = anchorName;
|
|
96
|
+
content.style.position = "absolute";
|
|
97
|
+
content.style.positionAnchor = anchorName;
|
|
98
|
+
content.style.positionTry = "normal flip-start";
|
|
99
|
+
const state = getState();
|
|
100
|
+
state?.mapped.set(anchor, floatable);
|
|
101
|
+
state?.mapped.set(content, floatable);
|
|
102
|
+
}
|
|
103
|
+
function isDisabled(element) {
|
|
104
|
+
return element.disabled || element.readOnly || element.getAttribute("aria-disabled") === "true";
|
|
105
|
+
}
|
|
106
|
+
function onClick(event) {
|
|
107
|
+
const related = findAncestor(event, SELECTOR_ALL);
|
|
108
|
+
if (!(related instanceof HTMLElement)) closeAll();
|
|
109
|
+
else if (related.hasAttribute(ATTRIBUTE_CONTENT)) closeAbove(related);
|
|
110
|
+
else if (!isDisabled(related)) toggle(related);
|
|
111
|
+
}
|
|
112
|
+
function onKeyDown(event) {
|
|
113
|
+
if (event.key !== "Escape") return;
|
|
114
|
+
const state = getState();
|
|
115
|
+
if (state == null || state.active.size === 0) return;
|
|
116
|
+
closeNonInteractive(state);
|
|
117
|
+
const related = findAncestor(event, SELECTOR_CONTENT);
|
|
118
|
+
if (!(related instanceof HTMLElement)) return;
|
|
119
|
+
closeAbove(related);
|
|
120
|
+
const floatable = state.mapped.get(related);
|
|
121
|
+
if (floatable != null) floatable.toggle(false);
|
|
122
|
+
}
|
|
123
|
+
function setPosition(floatable) {
|
|
124
|
+
let position = floatable.options.anchor.getAttribute(floatable.options.positionAttribute) ?? floatable.options.defaultPosition;
|
|
125
|
+
if (AREAS[position] == null) position = floatable.options.defaultPosition;
|
|
126
|
+
const area = AREAS[position];
|
|
127
|
+
floatable.options.content.style.positionArea = area;
|
|
128
|
+
floatable.options.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
|
|
129
|
+
floatable.options.onAfter?.(true);
|
|
130
|
+
}
|
|
131
|
+
function stop(floatable) {
|
|
132
|
+
if (floatable.frame != null) cancelAnimationFrame(floatable.frame);
|
|
133
|
+
floatable.frame = void 0;
|
|
134
|
+
}
|
|
135
|
+
function toggle(anchor) {
|
|
136
|
+
const floatable = getState()?.mapped.get(anchor);
|
|
137
|
+
if (floatable == null) {
|
|
138
|
+
closeAll();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const active = Boolean(floatable.active);
|
|
142
|
+
const content = findAncestor(floatable.options.anchor, SELECTOR_CONTENT);
|
|
143
|
+
if (content == null) closeAll();
|
|
144
|
+
else closeAbove(content);
|
|
145
|
+
floatable.toggle(!active);
|
|
146
|
+
}
|
|
147
|
+
const AREAS = {
|
|
148
|
+
above: "block-start",
|
|
149
|
+
"above-end": "start span-start",
|
|
150
|
+
"above-start": "start span-end",
|
|
151
|
+
below: "block-end",
|
|
152
|
+
"below-end": "end span-start",
|
|
153
|
+
"below-start": "end span-end",
|
|
154
|
+
end: "center end",
|
|
155
|
+
"end-bottom": "span-start end",
|
|
156
|
+
"end-top": "span-end end",
|
|
157
|
+
start: "center start",
|
|
158
|
+
"start-bottom": "span-start start",
|
|
159
|
+
"start-top": "span-end start"
|
|
160
|
+
};
|
|
161
|
+
const ATTRIBUTE_ANCHOR = "oui-floatable-anchor";
|
|
162
|
+
const ATTRIBUTE_CONTENT = "oui-floatable-content";
|
|
163
|
+
const ATTRIBUTE_POSITION = "oui-position";
|
|
164
|
+
const INSERT_AFTEREND = "afterend";
|
|
165
|
+
const POSITIONS = Object.fromEntries(Object.entries(AREAS).map(([position, area]) => [area, position]));
|
|
166
|
+
const SELECTOR_ALL = `[${ATTRIBUTE_ANCHOR}], [${ATTRIBUTE_CONTENT}]`;
|
|
167
|
+
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
168
|
+
let index = 0;
|
|
169
|
+
if (window._oscarpalmer_oui_floatable == null) {
|
|
170
|
+
Object.defineProperty(window, "_oscarpalmer_oui_floatable", {
|
|
171
|
+
enumerable: false,
|
|
172
|
+
value: {
|
|
173
|
+
active: /* @__PURE__ */ new Set(),
|
|
174
|
+
mapped: /* @__PURE__ */ new Map(),
|
|
175
|
+
order: []
|
|
176
|
+
},
|
|
177
|
+
writable: false
|
|
178
|
+
});
|
|
179
|
+
on(document, "keydown", (event) => {
|
|
180
|
+
onKeyDown(event);
|
|
181
|
+
});
|
|
182
|
+
on(document, "click", (event) => {
|
|
183
|
+
onClick(event);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
export { Floatable, deactivate };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { EventPosition, PlainObject } from "@oscarpalmer/atoms/models";
|
|
2
|
+
|
|
3
|
+
//#region src/js/sortable.d.ts
|
|
4
|
+
declare class Sortable extends Draggable {
|
|
5
|
+
connections: string | null;
|
|
6
|
+
origin: HTMLElement | undefined;
|
|
7
|
+
get dragged(): HTMLElement;
|
|
8
|
+
constructor(element: HTMLElement);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/js/internal/draggable.d.ts
|
|
12
|
+
declare abstract class Draggable {
|
|
13
|
+
prefix: string;
|
|
14
|
+
element: HTMLElement;
|
|
15
|
+
options: DraggableOptions;
|
|
16
|
+
container: HTMLElement | undefined;
|
|
17
|
+
hasHandles: boolean | undefined;
|
|
18
|
+
horizontal: boolean;
|
|
19
|
+
vertical: boolean;
|
|
20
|
+
abstract readonly dragged: HTMLElement;
|
|
21
|
+
abstract origin: HTMLElement | undefined;
|
|
22
|
+
constructor(prefix: string, element: HTMLElement, options: DraggableOptions);
|
|
23
|
+
destroy(): void;
|
|
24
|
+
}
|
|
25
|
+
type DragBegin = (event: MouseEvent | TouchEvent, state: DraggableState, draggable: Draggable, element: HTMLElement, handle: HTMLElement, position: EventPosition) => void;
|
|
26
|
+
type DragCancel = (state: DraggableState, draggable: Draggable) => void;
|
|
27
|
+
type DragEnd = (event: MouseEvent | TouchEvent, state: DraggableState, draggable: Draggable, reset: () => void) => void;
|
|
28
|
+
type DragMove = (event: MouseEvent | TouchEvent, state: DraggableState, draggable: Draggable, position: DragMovePosition) => PlainObject | undefined;
|
|
29
|
+
type DragMovePosition = {
|
|
30
|
+
calculated: EventPosition;
|
|
31
|
+
original: EventPosition;
|
|
32
|
+
};
|
|
33
|
+
type DraggableBound<Property extends keyof DraggableInstances = keyof DraggableInstances, Instance extends DraggableInstances[Property] = DraggableInstances[Property]> = {
|
|
34
|
+
constructor: new (element: HTMLElement) => Instance;
|
|
35
|
+
property: Property;
|
|
36
|
+
};
|
|
37
|
+
type DraggableInstances = {
|
|
38
|
+
movable?: Movable;
|
|
39
|
+
sortable?: Sortable;
|
|
40
|
+
};
|
|
41
|
+
type DraggableOptions = {
|
|
42
|
+
container: DraggableOptionsContainer;
|
|
43
|
+
direction: DraggableOptionsDirection;
|
|
44
|
+
drag: DraggableOptionsDrag;
|
|
45
|
+
position: DraggableOptionsPosition;
|
|
46
|
+
};
|
|
47
|
+
type DraggableOptionsContainer = {
|
|
48
|
+
attribute: string;
|
|
49
|
+
map: Map<HTMLElement, Set<Draggable>>;
|
|
50
|
+
onDestroy?: DraggableOptionsContainerOnDestroy;
|
|
51
|
+
onInitialize?: DraggableOptionsContainerOnInitialize;
|
|
52
|
+
};
|
|
53
|
+
type DraggableOptionsContainerOnDestroy = (draggable: Draggable, container: HTMLElement, empty: boolean) => void;
|
|
54
|
+
type DraggableOptionsContainerOnInitialize = (draggable: Draggable) => void;
|
|
55
|
+
type DraggableOptionsDirection = {
|
|
56
|
+
attribute: string;
|
|
57
|
+
onAfter?: DraggableOptionsDirectionOnAfter;
|
|
58
|
+
};
|
|
59
|
+
type DraggableOptionsDirectionOnAfter = (draggable: Draggable) => void;
|
|
60
|
+
type DraggableOptionsDrag = {
|
|
61
|
+
onBegin: DragBegin;
|
|
62
|
+
onCancel: DragCancel;
|
|
63
|
+
onEnd: DragEnd;
|
|
64
|
+
onMove: DragMove;
|
|
65
|
+
};
|
|
66
|
+
type DraggableOptionsPosition = {
|
|
67
|
+
getOffset: DraggableOptionsPositionGetOffset;
|
|
68
|
+
getOriginal: DraggableOptionsPositionGetOriginal;
|
|
69
|
+
};
|
|
70
|
+
type DraggableOptionsPositionGetOffset = (state: DraggableState, draggable: Draggable, element: HTMLElement, position: EventPosition) => EventPosition;
|
|
71
|
+
type DraggableOptionsPositionGetOriginal = (state: DraggableState, draggable: Draggable, position: EventPosition) => EventPosition;
|
|
72
|
+
type DraggableState = {
|
|
73
|
+
container: DOMRect | undefined;
|
|
74
|
+
element: DraggableStateElement | undefined;
|
|
75
|
+
instances: WeakMap<HTMLElement, DraggableInstances>;
|
|
76
|
+
offset: EventPosition | undefined;
|
|
77
|
+
original: EventPosition | undefined;
|
|
78
|
+
};
|
|
79
|
+
type DraggableStateElement = {
|
|
80
|
+
node: HTMLElement;
|
|
81
|
+
rectangle: DOMRect;
|
|
82
|
+
};
|
|
83
|
+
declare function addDraggable<Property extends keyof DraggableInstances, Instance extends DraggableInstances[Property]>(this: DraggableBound<Property, Instance>, element: HTMLElement): void;
|
|
84
|
+
declare function removeDraggable<Property extends keyof DraggableInstances, Instance extends DraggableInstances[Property]>(this: {
|
|
85
|
+
constructor: new (element: HTMLElement) => Instance;
|
|
86
|
+
property: Property;
|
|
87
|
+
}, element: HTMLElement): void;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/js/movable.d.ts
|
|
90
|
+
declare class Movable extends Draggable {
|
|
91
|
+
inset: string;
|
|
92
|
+
moved: boolean;
|
|
93
|
+
offset: Partial<EventPosition> | undefined;
|
|
94
|
+
position: string;
|
|
95
|
+
get dragged(): HTMLElement;
|
|
96
|
+
get origin(): HTMLElement | undefined;
|
|
97
|
+
constructor(element: HTMLElement);
|
|
98
|
+
destroy(): void;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { DraggableState as a, Sortable as c, DraggableBound as i, DragMovePosition as n, addDraggable as o, Draggable as r, removeDraggable as s, Movable as t };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { attributable } from "./internal/attributable.mjs";
|
|
2
|
+
import { Draggable, addDraggable, removeDraggable } from "./internal/draggable.mjs";
|
|
3
|
+
import { getPosition, on } from "@oscarpalmer/toretto/event";
|
|
4
|
+
//#region src/js/movable.ts
|
|
5
|
+
var Movable = class extends Draggable {
|
|
6
|
+
inset;
|
|
7
|
+
moved = false;
|
|
8
|
+
offset;
|
|
9
|
+
position;
|
|
10
|
+
get dragged() {
|
|
11
|
+
return this.element;
|
|
12
|
+
}
|
|
13
|
+
get origin() {
|
|
14
|
+
return this.element;
|
|
15
|
+
}
|
|
16
|
+
constructor(element) {
|
|
17
|
+
super("move", element, {
|
|
18
|
+
container: {
|
|
19
|
+
attribute: ATTRIBUTE_CONTAINER,
|
|
20
|
+
map: CONTAINERS,
|
|
21
|
+
onDestroy: destroyContainer,
|
|
22
|
+
onInitialize: initializeContainer
|
|
23
|
+
},
|
|
24
|
+
direction: {
|
|
25
|
+
attribute: ATTRIBUTE_DIRECTION,
|
|
26
|
+
onAfter: afterDirection
|
|
27
|
+
},
|
|
28
|
+
drag: {
|
|
29
|
+
onBegin,
|
|
30
|
+
onCancel,
|
|
31
|
+
onEnd,
|
|
32
|
+
onMove
|
|
33
|
+
},
|
|
34
|
+
position: {
|
|
35
|
+
getOffset: getOffsetPosition,
|
|
36
|
+
getOriginal: getOriginalPosition
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
this.hasHandles = element.querySelector(SELECTOR_HANDLE) != null;
|
|
40
|
+
this.inset = element.style.inset;
|
|
41
|
+
this.position = element.style.position;
|
|
42
|
+
}
|
|
43
|
+
destroy() {
|
|
44
|
+
super.destroy();
|
|
45
|
+
MOVED.delete(this);
|
|
46
|
+
this.element.style.inset = this.inset;
|
|
47
|
+
this.element.style.position = this.position;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function afterDirection(draggable) {
|
|
51
|
+
const container = draggable.container?.getBoundingClientRect();
|
|
52
|
+
if (container != null) setOffset(draggable, container);
|
|
53
|
+
}
|
|
54
|
+
function destroyContainer(_, container, empty) {
|
|
55
|
+
if (empty) resizer.unobserve(container);
|
|
56
|
+
}
|
|
57
|
+
function getOffsetPosition(state, _, __, position) {
|
|
58
|
+
return {
|
|
59
|
+
x: position.x - (state.element?.rectangle.left ?? 0),
|
|
60
|
+
y: position.y - (state.element?.rectangle.top ?? 0)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function getOriginalPosition(state) {
|
|
64
|
+
return {
|
|
65
|
+
x: state.element?.rectangle.left ?? 0,
|
|
66
|
+
y: state.element?.rectangle.top ?? 0
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function initializeContainer(draggable) {
|
|
70
|
+
resizer.observe(draggable.container);
|
|
71
|
+
}
|
|
72
|
+
function onBegin() {}
|
|
73
|
+
function onCancel(state, draggable) {
|
|
74
|
+
const { element, inset, moved, position } = draggable;
|
|
75
|
+
const { x, y } = state.original ?? {
|
|
76
|
+
x: 0,
|
|
77
|
+
y: 0
|
|
78
|
+
};
|
|
79
|
+
if (moved) element.style.inset = `${y}px auto auto ${x}px`;
|
|
80
|
+
else {
|
|
81
|
+
element.style.position = position;
|
|
82
|
+
element.style.inset = inset;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function onEnd(event, state, draggable, reset) {
|
|
86
|
+
const movable = draggable;
|
|
87
|
+
if (!movable.moved) {
|
|
88
|
+
movable.moved = true;
|
|
89
|
+
if (movable.container != null) MOVED.add(movable);
|
|
90
|
+
}
|
|
91
|
+
const container = movable.container?.getBoundingClientRect();
|
|
92
|
+
if (container != null) setOffset(movable, container);
|
|
93
|
+
const end = new CustomEvent(EVENT_END, {
|
|
94
|
+
detail: {
|
|
95
|
+
from: { ...state.original },
|
|
96
|
+
to: getPosition(event)
|
|
97
|
+
},
|
|
98
|
+
cancelable: true
|
|
99
|
+
});
|
|
100
|
+
movable.element.dispatchEvent(end);
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
if (end.defaultPrevented) onCancel(state, draggable);
|
|
103
|
+
reset();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function onMove(_, state, __, position) {
|
|
107
|
+
return {
|
|
108
|
+
from: { ...state.original },
|
|
109
|
+
to: { ...position.calculated }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function onResize(entries) {
|
|
113
|
+
const { length } = entries;
|
|
114
|
+
for (let index = 0; index < length; index += 1) {
|
|
115
|
+
const entry = entries[index];
|
|
116
|
+
const set = CONTAINERS.get(entry.target);
|
|
117
|
+
if (set == null) continue;
|
|
118
|
+
for (const movable of set) reposition(movable);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function onScroll() {
|
|
122
|
+
for (const moved of MOVED) reposition(moved);
|
|
123
|
+
}
|
|
124
|
+
function reposition(movable) {
|
|
125
|
+
if (!movable.moved || movable.container == null) return;
|
|
126
|
+
const container = movable.container.getBoundingClientRect();
|
|
127
|
+
const x = container.left + (movable.offset?.x ?? 0);
|
|
128
|
+
const y = container.top + (movable.offset?.y ?? 0);
|
|
129
|
+
movable.element.style.inset = `${y}px auto auto ${x}px`;
|
|
130
|
+
}
|
|
131
|
+
function setOffset(movable, container) {
|
|
132
|
+
movable.offset = {};
|
|
133
|
+
const element = movable.element.getBoundingClientRect();
|
|
134
|
+
movable.offset.y = element.top - container.top;
|
|
135
|
+
movable.offset.x = element.left - container.left;
|
|
136
|
+
}
|
|
137
|
+
const ATTRIBUTE = "oui-movable";
|
|
138
|
+
const ATTRIBUTE_CONTAINER = `${ATTRIBUTE}-container`;
|
|
139
|
+
const ATTRIBUTE_DIRECTION = `${ATTRIBUTE}-direction`;
|
|
140
|
+
const CONTAINERS = /* @__PURE__ */ new Map();
|
|
141
|
+
const EVENT_END = "move:end";
|
|
142
|
+
const MOVED = /* @__PURE__ */ new Set();
|
|
143
|
+
const SELECTOR_HANDLE = `[${ATTRIBUTE}-handle]`;
|
|
144
|
+
const bound = {
|
|
145
|
+
constructor: Movable,
|
|
146
|
+
property: "movable"
|
|
147
|
+
};
|
|
148
|
+
const resizer = new ResizeObserver(onResize);
|
|
149
|
+
attributable(ATTRIBUTE, addDraggable.bind(bound), removeDraggable.bind(bound));
|
|
150
|
+
on(document, "scroll", onScroll);
|
|
151
|
+
//#endregion
|
|
152
|
+
export { Movable };
|