@blcklab/freedom 0.1.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/README.md +240 -0
- package/dist/index.cjs +798 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +791 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.cjs +94 -0
- package/dist/manager.cjs.map +1 -0
- package/dist/manager.d.cts +6 -0
- package/dist/manager.d.ts +6 -0
- package/dist/manager.js +91 -0
- package/dist/manager.js.map +1 -0
- package/dist/snap.cjs +34 -0
- package/dist/snap.cjs.map +1 -0
- package/dist/snap.d.cts +10 -0
- package/dist/snap.d.ts +10 -0
- package/dist/snap.js +32 -0
- package/dist/snap.js.map +1 -0
- package/dist/types-tH0wv_P8.d.cts +121 -0
- package/dist/types-tH0wv_P8.d.ts +121 -0
- package/dist/window.cjs +788 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.d.cts +6 -0
- package/dist/window.d.ts +6 -0
- package/dist/window.js +785 -0
- package/dist/window.js.map +1 -0
- package/package.json +65 -0
package/dist/window.js
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
// src/core/events.ts
|
|
2
|
+
var Emitter = class {
|
|
3
|
+
listeners = /* @__PURE__ */ new Map();
|
|
4
|
+
on(event, handler) {
|
|
5
|
+
let set = this.listeners.get(event);
|
|
6
|
+
if (!set) {
|
|
7
|
+
set = /* @__PURE__ */ new Set();
|
|
8
|
+
this.listeners.set(event, set);
|
|
9
|
+
}
|
|
10
|
+
set.add(handler);
|
|
11
|
+
return () => this.off(event, handler);
|
|
12
|
+
}
|
|
13
|
+
off(event, handler) {
|
|
14
|
+
this.listeners.get(event)?.delete(handler);
|
|
15
|
+
}
|
|
16
|
+
emit(event, data) {
|
|
17
|
+
const set = this.listeners.get(event);
|
|
18
|
+
if (!set || set.size === 0) return;
|
|
19
|
+
for (const handler of [...set]) {
|
|
20
|
+
handler(data);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.listeners.clear();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/core/math.ts
|
|
29
|
+
function clamp(value, min, max) {
|
|
30
|
+
if (min > max) return min;
|
|
31
|
+
return Math.min(Math.max(value, min), max);
|
|
32
|
+
}
|
|
33
|
+
function clampSize(size, limits) {
|
|
34
|
+
return {
|
|
35
|
+
width: clamp(size.width, limits.minWidth, Math.max(limits.minWidth, limits.maxWidth)),
|
|
36
|
+
height: clamp(size.height, limits.minHeight, Math.max(limits.minHeight, limits.maxHeight))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function clampPointToBounds(point, size, bounds) {
|
|
40
|
+
const maxX = Math.max(bounds.minX, bounds.maxX - size.width);
|
|
41
|
+
const maxY = Math.max(bounds.minY, bounds.maxY - size.height);
|
|
42
|
+
return {
|
|
43
|
+
x: clamp(point.x, bounds.minX, maxX),
|
|
44
|
+
y: clamp(point.y, bounds.minY, maxY)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/engine/constraints.ts
|
|
49
|
+
function resolveBounds(option, element) {
|
|
50
|
+
if (!option || option === "none") return null;
|
|
51
|
+
if (option === "viewport") {
|
|
52
|
+
return { minX: 0, minY: 0, maxX: window.innerWidth, maxY: window.innerHeight };
|
|
53
|
+
}
|
|
54
|
+
if (option === "parent") {
|
|
55
|
+
const parent = element.offsetParent;
|
|
56
|
+
if (!parent) return null;
|
|
57
|
+
return { minX: 0, minY: 0, maxX: parent.clientWidth, maxY: parent.clientHeight };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
minX: option.x,
|
|
61
|
+
minY: option.y,
|
|
62
|
+
maxX: option.x + option.width,
|
|
63
|
+
maxY: option.y + option.height
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function constrainToBounds(point, size, option, element) {
|
|
67
|
+
const bounds = resolveBounds(option, element);
|
|
68
|
+
return bounds ? clampPointToBounds(point, size, bounds) : point;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/engine/drag.ts
|
|
72
|
+
function createDragEngine(options) {
|
|
73
|
+
let startPointer = { x: 0, y: 0 };
|
|
74
|
+
let startPosition = { x: 0, y: 0 };
|
|
75
|
+
return {
|
|
76
|
+
begin(pointer, pointerEvent) {
|
|
77
|
+
startPointer = pointer;
|
|
78
|
+
startPosition = options.getPosition();
|
|
79
|
+
return { position: startPosition, delta: { x: 0, y: 0 }, pointerEvent };
|
|
80
|
+
},
|
|
81
|
+
move(pointer, pointerEvent) {
|
|
82
|
+
const delta = {
|
|
83
|
+
x: pointer.x - startPointer.x,
|
|
84
|
+
y: pointer.y - startPointer.y
|
|
85
|
+
};
|
|
86
|
+
let next = { x: startPosition.x + delta.x, y: startPosition.y + delta.y };
|
|
87
|
+
next = constrainToBounds(next, options.getSize(), options.bounds, options.element);
|
|
88
|
+
for (const plugin of options.plugins) {
|
|
89
|
+
if (!plugin.onDrag) continue;
|
|
90
|
+
const result = plugin.onDrag(
|
|
91
|
+
{ position: next, delta, pointerEvent },
|
|
92
|
+
options.pluginContext
|
|
93
|
+
);
|
|
94
|
+
if (result) next = result;
|
|
95
|
+
}
|
|
96
|
+
return { position: next, data: { position: next, delta, pointerEvent } };
|
|
97
|
+
},
|
|
98
|
+
end(pointer, pointerEvent) {
|
|
99
|
+
const delta = {
|
|
100
|
+
x: pointer.x - startPointer.x,
|
|
101
|
+
y: pointer.y - startPointer.y
|
|
102
|
+
};
|
|
103
|
+
return { position: options.getPosition(), delta, pointerEvent };
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/engine/resize.ts
|
|
109
|
+
var WEST_HANDLES = ["nw", "w", "sw"];
|
|
110
|
+
var NORTH_HANDLES = ["nw", "n", "ne"];
|
|
111
|
+
var VERTICAL_ONLY_HANDLES = ["n", "s"];
|
|
112
|
+
var HORIZONTAL_ONLY_HANDLES = ["e", "w"];
|
|
113
|
+
function createResizeEngine(options) {
|
|
114
|
+
let activeHandle = "se";
|
|
115
|
+
let startPointer = { x: 0, y: 0 };
|
|
116
|
+
let startPosition = { x: 0, y: 0 };
|
|
117
|
+
let startSize = { width: 0, height: 0 };
|
|
118
|
+
let affectsWest = false;
|
|
119
|
+
let affectsNorth = false;
|
|
120
|
+
let affectsWidth = true;
|
|
121
|
+
let affectsHeight = true;
|
|
122
|
+
return {
|
|
123
|
+
begin(handle, pointer, pointerEvent) {
|
|
124
|
+
activeHandle = handle;
|
|
125
|
+
affectsWest = WEST_HANDLES.includes(handle);
|
|
126
|
+
affectsNorth = NORTH_HANDLES.includes(handle);
|
|
127
|
+
affectsWidth = !VERTICAL_ONLY_HANDLES.includes(handle);
|
|
128
|
+
affectsHeight = !HORIZONTAL_ONLY_HANDLES.includes(handle);
|
|
129
|
+
startPointer = pointer;
|
|
130
|
+
startPosition = options.getPosition();
|
|
131
|
+
startSize = options.getSize();
|
|
132
|
+
return { size: startSize, position: startPosition, handle, pointerEvent };
|
|
133
|
+
},
|
|
134
|
+
move(pointer, pointerEvent) {
|
|
135
|
+
const dx = pointer.x - startPointer.x;
|
|
136
|
+
const dy = pointer.y - startPointer.y;
|
|
137
|
+
let x = startPosition.x;
|
|
138
|
+
let y = startPosition.y;
|
|
139
|
+
const width = affectsWidth ? affectsWest ? startSize.width - dx : startSize.width + dx : startSize.width;
|
|
140
|
+
const height = affectsHeight ? affectsNorth ? startSize.height - dy : startSize.height + dy : startSize.height;
|
|
141
|
+
const clampedWidth = clamp(width, options.minWidth, Math.max(options.minWidth, options.maxWidth));
|
|
142
|
+
const clampedHeight = clamp(height, options.minHeight, Math.max(options.minHeight, options.maxHeight));
|
|
143
|
+
if (affectsWest) x = startPosition.x + (startSize.width - clampedWidth);
|
|
144
|
+
if (affectsNorth) y = startPosition.y + (startSize.height - clampedHeight);
|
|
145
|
+
let nextSize = { width: clampedWidth, height: clampedHeight };
|
|
146
|
+
let nextPosition = constrainToBounds({ x, y }, nextSize, options.bounds, options.element);
|
|
147
|
+
for (const plugin of options.plugins) {
|
|
148
|
+
if (!plugin.onResize) continue;
|
|
149
|
+
const result = plugin.onResize(
|
|
150
|
+
{ size: nextSize, position: nextPosition, handle: activeHandle, pointerEvent },
|
|
151
|
+
options.pluginContext
|
|
152
|
+
);
|
|
153
|
+
if (result) {
|
|
154
|
+
nextSize = result.size;
|
|
155
|
+
nextPosition = result.position;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
position: nextPosition,
|
|
160
|
+
size: nextSize,
|
|
161
|
+
data: { size: nextSize, position: nextPosition, handle: activeHandle, pointerEvent }
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
end(pointer, pointerEvent) {
|
|
165
|
+
return {
|
|
166
|
+
size: options.getSize(),
|
|
167
|
+
position: options.getPosition(),
|
|
168
|
+
handle: activeHandle,
|
|
169
|
+
pointerEvent
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/core/interaction-manager.ts
|
|
176
|
+
function toPoint(event) {
|
|
177
|
+
return { x: event.clientX, y: event.clientY };
|
|
178
|
+
}
|
|
179
|
+
var InteractionManager = class {
|
|
180
|
+
options;
|
|
181
|
+
activePointerId = null;
|
|
182
|
+
captureTarget = null;
|
|
183
|
+
_active = null;
|
|
184
|
+
destroyed = false;
|
|
185
|
+
constructor(options) {
|
|
186
|
+
this.options = options;
|
|
187
|
+
this.options.element.addEventListener("pointerdown", this.handlePointerDown);
|
|
188
|
+
}
|
|
189
|
+
get active() {
|
|
190
|
+
return this._active;
|
|
191
|
+
}
|
|
192
|
+
startDrag(event) {
|
|
193
|
+
if (this.destroyed || this._active !== null) return;
|
|
194
|
+
this._active = "drag";
|
|
195
|
+
this.captureGesture(event);
|
|
196
|
+
const data = this.options.dragEngine.begin(toPoint(event), event);
|
|
197
|
+
this.options.onDragStart(data);
|
|
198
|
+
}
|
|
199
|
+
startResize(handle, event) {
|
|
200
|
+
if (this.destroyed || this._active !== null) return;
|
|
201
|
+
this._active = "resize";
|
|
202
|
+
this.captureGesture(event);
|
|
203
|
+
const data = this.options.resizeEngine.begin(handle, toPoint(event), event);
|
|
204
|
+
this.options.onResizeStart(data);
|
|
205
|
+
}
|
|
206
|
+
cancel() {
|
|
207
|
+
this.teardown();
|
|
208
|
+
}
|
|
209
|
+
destroy() {
|
|
210
|
+
if (this.destroyed) return;
|
|
211
|
+
this.destroyed = true;
|
|
212
|
+
this.options.element.removeEventListener("pointerdown", this.handlePointerDown);
|
|
213
|
+
this.teardown();
|
|
214
|
+
}
|
|
215
|
+
captureGesture(event) {
|
|
216
|
+
this.activePointerId = event.pointerId;
|
|
217
|
+
this.captureTarget = event.target instanceof Element ? event.target : this.options.element;
|
|
218
|
+
try {
|
|
219
|
+
this.captureTarget.setPointerCapture?.(event.pointerId);
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
window.addEventListener("pointermove", this.handlePointerMove);
|
|
223
|
+
window.addEventListener("pointerup", this.handlePointerUp);
|
|
224
|
+
window.addEventListener("pointercancel", this.handlePointerUp);
|
|
225
|
+
}
|
|
226
|
+
teardown() {
|
|
227
|
+
if (this.activePointerId !== null && this.captureTarget) {
|
|
228
|
+
try {
|
|
229
|
+
this.captureTarget.releasePointerCapture?.(this.activePointerId);
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
this.activePointerId = null;
|
|
234
|
+
this.captureTarget = null;
|
|
235
|
+
this._active = null;
|
|
236
|
+
window.removeEventListener("pointermove", this.handlePointerMove);
|
|
237
|
+
window.removeEventListener("pointerup", this.handlePointerUp);
|
|
238
|
+
window.removeEventListener("pointercancel", this.handlePointerUp);
|
|
239
|
+
}
|
|
240
|
+
handlePointerDown = (event) => {
|
|
241
|
+
if (this.destroyed || this._active !== null) return;
|
|
242
|
+
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
243
|
+
const resizeHandle = this.options.resolveResizeHandle(event.target);
|
|
244
|
+
if (resizeHandle) {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
event.stopPropagation();
|
|
247
|
+
this.startResize(resizeHandle, event);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (this.options.isDragTarget(event.target)) {
|
|
251
|
+
this.startDrag(event);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
handlePointerMove = (event) => {
|
|
255
|
+
if (this.destroyed || event.pointerId !== this.activePointerId) return;
|
|
256
|
+
if (this._active === "drag") {
|
|
257
|
+
this.options.onDragMove(this.options.dragEngine.move(toPoint(event), event));
|
|
258
|
+
} else if (this._active === "resize") {
|
|
259
|
+
this.options.onResizeMove(this.options.resizeEngine.move(toPoint(event), event));
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
handlePointerUp = (event) => {
|
|
263
|
+
if (this.destroyed || event.pointerId !== this.activePointerId) return;
|
|
264
|
+
if (this._active === "drag") {
|
|
265
|
+
this.options.onDragEnd(this.options.dragEngine.end(toPoint(event), event));
|
|
266
|
+
} else if (this._active === "resize") {
|
|
267
|
+
this.options.onResizeEnd(this.options.resizeEngine.end(toPoint(event), event));
|
|
268
|
+
}
|
|
269
|
+
this.teardown();
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
function createInteractionManager(options) {
|
|
273
|
+
return new InteractionManager(options);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/dom/render.ts
|
|
277
|
+
function applyBaseStyles(element, options) {
|
|
278
|
+
const style = element.style;
|
|
279
|
+
const computedPosition = window.getComputedStyle?.(element).position ?? style.position;
|
|
280
|
+
if (options.forcePositioning || computedPosition === "static") {
|
|
281
|
+
style.position = options.positioning;
|
|
282
|
+
}
|
|
283
|
+
if (!style.touchAction) style.touchAction = "none";
|
|
284
|
+
if (!style.userSelect) style.userSelect = "none";
|
|
285
|
+
if (!style.boxSizing) style.boxSizing = "border-box";
|
|
286
|
+
if (!style.willChange) style.willChange = "transform";
|
|
287
|
+
}
|
|
288
|
+
function readRenderedPosition(element, positioning) {
|
|
289
|
+
const rect = element.getBoundingClientRect();
|
|
290
|
+
if (positioning === "fixed") {
|
|
291
|
+
return { x: rect.left || 0, y: rect.top || 0 };
|
|
292
|
+
}
|
|
293
|
+
if (positioning === "absolute") {
|
|
294
|
+
const parent = element.offsetParent;
|
|
295
|
+
if (parent) {
|
|
296
|
+
const parentRect = parent.getBoundingClientRect();
|
|
297
|
+
return {
|
|
298
|
+
x: (rect.left || 0) - (parentRect.left || 0) + (parent.scrollLeft || 0),
|
|
299
|
+
y: (rect.top || 0) - (parentRect.top || 0) + (parent.scrollTop || 0)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
x: (rect.left || 0) + (window.scrollX || 0),
|
|
304
|
+
y: (rect.top || 0) + (window.scrollY || 0)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return { x: rect.left || 0, y: rect.top || 0 };
|
|
308
|
+
}
|
|
309
|
+
function createPositionRenderer(element, positioning, initialPosition) {
|
|
310
|
+
const baseTransform = normalizeTransform(element.style.transform);
|
|
311
|
+
if (positioning === "relative") {
|
|
312
|
+
return {
|
|
313
|
+
positioning,
|
|
314
|
+
origin: readRenderedPosition(element, positioning),
|
|
315
|
+
baseTransform,
|
|
316
|
+
flowRelative: true
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
element.style.left = `${initialPosition.x}px`;
|
|
320
|
+
element.style.top = `${initialPosition.y}px`;
|
|
321
|
+
element.style.right = "auto";
|
|
322
|
+
element.style.bottom = "auto";
|
|
323
|
+
return {
|
|
324
|
+
positioning,
|
|
325
|
+
origin: { ...initialPosition },
|
|
326
|
+
baseTransform: "",
|
|
327
|
+
flowRelative: false
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function writePosition(element, point, renderer) {
|
|
331
|
+
const dx = point.x - renderer.origin.x;
|
|
332
|
+
const dy = point.y - renderer.origin.y;
|
|
333
|
+
const translate = dx === 0 && dy === 0 ? "" : `translate3d(${dx}px, ${dy}px, 0)`;
|
|
334
|
+
element.style.transform = joinTransforms(renderer.baseTransform, translate);
|
|
335
|
+
}
|
|
336
|
+
function writeSize(element, size) {
|
|
337
|
+
element.style.width = `${size.width}px`;
|
|
338
|
+
element.style.height = `${size.height}px`;
|
|
339
|
+
}
|
|
340
|
+
function normalizeTransform(value) {
|
|
341
|
+
if (!value || value === "none") return "";
|
|
342
|
+
return value.trim();
|
|
343
|
+
}
|
|
344
|
+
function joinTransforms(base, translate) {
|
|
345
|
+
if (base && translate) return `${base} ${translate}`;
|
|
346
|
+
if (base) return base;
|
|
347
|
+
return translate;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/dom/handles.ts
|
|
351
|
+
var CORNER_SIZE_PX = 14;
|
|
352
|
+
var EDGE_THICKNESS_PX = 8;
|
|
353
|
+
var EDGE_STYLES = {
|
|
354
|
+
nw: { top: "0", left: "0", width: `${CORNER_SIZE_PX}px`, height: `${CORNER_SIZE_PX}px`, cursor: "nwse-resize" },
|
|
355
|
+
ne: { top: "0", right: "0", width: `${CORNER_SIZE_PX}px`, height: `${CORNER_SIZE_PX}px`, cursor: "nesw-resize" },
|
|
356
|
+
sw: { bottom: "0", left: "0", width: `${CORNER_SIZE_PX}px`, height: `${CORNER_SIZE_PX}px`, cursor: "nesw-resize" },
|
|
357
|
+
se: { bottom: "0", right: "0", width: `${CORNER_SIZE_PX}px`, height: `${CORNER_SIZE_PX}px`, cursor: "nwse-resize" },
|
|
358
|
+
n: { top: "0", left: `${CORNER_SIZE_PX}px`, right: `${CORNER_SIZE_PX}px`, height: `${EDGE_THICKNESS_PX}px`, cursor: "ns-resize" },
|
|
359
|
+
s: { bottom: "0", left: `${CORNER_SIZE_PX}px`, right: `${CORNER_SIZE_PX}px`, height: `${EDGE_THICKNESS_PX}px`, cursor: "ns-resize" },
|
|
360
|
+
e: { right: "0", top: `${CORNER_SIZE_PX}px`, bottom: `${CORNER_SIZE_PX}px`, width: `${EDGE_THICKNESS_PX}px`, cursor: "ew-resize" },
|
|
361
|
+
w: { left: "0", top: `${CORNER_SIZE_PX}px`, bottom: `${CORNER_SIZE_PX}px`, width: `${EDGE_THICKNESS_PX}px`, cursor: "ew-resize" }
|
|
362
|
+
};
|
|
363
|
+
function createResizeHandle(handle) {
|
|
364
|
+
const el = document.createElement("div");
|
|
365
|
+
el.dataset.freedomResizeHandle = handle;
|
|
366
|
+
el.className = `freedom-resize-handle freedom-resize-handle--${handle}`;
|
|
367
|
+
Object.assign(el.style, {
|
|
368
|
+
position: "absolute",
|
|
369
|
+
boxSizing: "border-box",
|
|
370
|
+
touchAction: "none",
|
|
371
|
+
userSelect: "none",
|
|
372
|
+
pointerEvents: "auto",
|
|
373
|
+
zIndex: "1",
|
|
374
|
+
...EDGE_STYLES[handle]
|
|
375
|
+
});
|
|
376
|
+
return el;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/dom/scheduler.ts
|
|
380
|
+
function createFrameScheduler(flush) {
|
|
381
|
+
let frameHandle = null;
|
|
382
|
+
return {
|
|
383
|
+
schedule() {
|
|
384
|
+
if (frameHandle !== null) return;
|
|
385
|
+
frameHandle = requestAnimationFrame(() => {
|
|
386
|
+
frameHandle = null;
|
|
387
|
+
flush();
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
cancel() {
|
|
391
|
+
if (frameHandle !== null) {
|
|
392
|
+
cancelAnimationFrame(frameHandle);
|
|
393
|
+
frameHandle = null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/runtime/window.ts
|
|
400
|
+
var ALL_HANDLES = ["n", "s", "e", "w", "nw", "ne", "sw", "se"];
|
|
401
|
+
var instances = /* @__PURE__ */ new WeakMap();
|
|
402
|
+
var autoId = 0;
|
|
403
|
+
function createWindow(element, options = {}) {
|
|
404
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
405
|
+
throw new Error("freedom.window() requires a browser environment. Importing is SSR-safe, but creating a window must run in the browser.");
|
|
406
|
+
}
|
|
407
|
+
assertHTMLElement(element);
|
|
408
|
+
if (instances.has(element)) {
|
|
409
|
+
throw new Error("freedom.window() was called more than once for the same element. Destroy the existing instance before creating a new one.");
|
|
410
|
+
}
|
|
411
|
+
const id = options.id ?? `freedom-window-${++autoId}`;
|
|
412
|
+
const emitter = new Emitter();
|
|
413
|
+
const plugins = options.plugins ?? [];
|
|
414
|
+
const limits = normalizeSizeLimits(options);
|
|
415
|
+
const positioning = resolvePositioning(element, options);
|
|
416
|
+
applyBaseStyles(element, {
|
|
417
|
+
positioning,
|
|
418
|
+
forcePositioning: shouldForcePositioning(element, options)
|
|
419
|
+
});
|
|
420
|
+
let size = clampSize(sanitizeSize(options.initialSize ?? readInitialSize(element), "initialSize"), limits);
|
|
421
|
+
let position = resolveInitialPosition(options.initialPosition, element, size, options.bounds, positioning);
|
|
422
|
+
const renderer = createPositionRenderer(element, positioning, position);
|
|
423
|
+
let zIndex = normalizeZIndex(options.zIndex ?? 0);
|
|
424
|
+
let focused = false;
|
|
425
|
+
let isDraggable = options.draggable ?? true;
|
|
426
|
+
let isDestroyed = false;
|
|
427
|
+
writeSize(element, size);
|
|
428
|
+
writePosition(element, position, renderer);
|
|
429
|
+
if (zIndex) element.style.zIndex = String(zIndex);
|
|
430
|
+
revealIfRequested(element, options);
|
|
431
|
+
let pendingPosition = null;
|
|
432
|
+
let pendingSize = null;
|
|
433
|
+
const scheduler = createFrameScheduler(() => {
|
|
434
|
+
if (isDestroyed) return;
|
|
435
|
+
if (pendingPosition) {
|
|
436
|
+
writePosition(element, pendingPosition, renderer);
|
|
437
|
+
pendingPosition = null;
|
|
438
|
+
}
|
|
439
|
+
if (pendingSize) {
|
|
440
|
+
writeSize(element, pendingSize);
|
|
441
|
+
pendingSize = null;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
function paint(nextPosition, nextSize) {
|
|
445
|
+
if (nextPosition) pendingPosition = nextPosition;
|
|
446
|
+
if (nextSize) pendingSize = nextSize;
|
|
447
|
+
scheduler.schedule();
|
|
448
|
+
}
|
|
449
|
+
function assertAlive(method) {
|
|
450
|
+
if (isDestroyed) {
|
|
451
|
+
throw new Error(`Cannot call ${method}() on a destroyed freedom window.`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const pluginContext = {
|
|
455
|
+
element,
|
|
456
|
+
get window() {
|
|
457
|
+
return api;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const dragHandleElement = resolveDragHandle(element, options.dragHandle);
|
|
461
|
+
function isDragTarget(target) {
|
|
462
|
+
if (!isDraggable || !dragHandleElement) return false;
|
|
463
|
+
if (!(target instanceof Node)) return false;
|
|
464
|
+
return dragHandleElement === target || dragHandleElement.contains(target);
|
|
465
|
+
}
|
|
466
|
+
const dragEngine = createDragEngine({
|
|
467
|
+
element,
|
|
468
|
+
bounds: options.bounds,
|
|
469
|
+
plugins,
|
|
470
|
+
pluginContext,
|
|
471
|
+
getPosition: () => position,
|
|
472
|
+
getSize: () => size
|
|
473
|
+
});
|
|
474
|
+
const resizeHandleElements = /* @__PURE__ */ new Map();
|
|
475
|
+
function resolveResizeHandle(target) {
|
|
476
|
+
if (!(target instanceof Element)) return null;
|
|
477
|
+
const handleEl = target.closest(".freedom-resize-handle");
|
|
478
|
+
if (!handleEl) return null;
|
|
479
|
+
for (const [handle, el] of resizeHandleElements) {
|
|
480
|
+
if (el === handleEl) return handle;
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const resizeEngine = createResizeEngine({
|
|
485
|
+
element,
|
|
486
|
+
bounds: options.bounds,
|
|
487
|
+
...limits,
|
|
488
|
+
plugins,
|
|
489
|
+
pluginContext,
|
|
490
|
+
getPosition: () => position,
|
|
491
|
+
getSize: () => size
|
|
492
|
+
});
|
|
493
|
+
function setupResizeHandles(handles) {
|
|
494
|
+
const normalizedHandles = normalizeResizeHandles(handles);
|
|
495
|
+
for (const handle of normalizedHandles) {
|
|
496
|
+
if (resizeHandleElements.has(handle)) continue;
|
|
497
|
+
const handleEl = createResizeHandle(handle);
|
|
498
|
+
element.appendChild(handleEl);
|
|
499
|
+
resizeHandleElements.set(handle, handleEl);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function teardownResizeHandles(handles = ALL_HANDLES) {
|
|
503
|
+
for (const handle of handles) {
|
|
504
|
+
const handleEl = resizeHandleElements.get(handle);
|
|
505
|
+
if (handleEl) {
|
|
506
|
+
handleEl.remove();
|
|
507
|
+
resizeHandleElements.delete(handle);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const interactionManager = createInteractionManager({
|
|
512
|
+
element,
|
|
513
|
+
dragEngine,
|
|
514
|
+
resizeEngine,
|
|
515
|
+
resolveResizeHandle,
|
|
516
|
+
isDragTarget,
|
|
517
|
+
onDragStart(data) {
|
|
518
|
+
element.classList.add("freedom-dragging");
|
|
519
|
+
emitter.emit("dragstart", data);
|
|
520
|
+
},
|
|
521
|
+
onDragMove({ position: next, data }) {
|
|
522
|
+
position = next;
|
|
523
|
+
paint(position);
|
|
524
|
+
emitter.emit("drag", data);
|
|
525
|
+
},
|
|
526
|
+
onDragEnd(data) {
|
|
527
|
+
element.classList.remove("freedom-dragging");
|
|
528
|
+
emitter.emit("dragend", data);
|
|
529
|
+
},
|
|
530
|
+
onResizeStart(data) {
|
|
531
|
+
element.classList.add("freedom-resizing");
|
|
532
|
+
emitter.emit("resizestart", data);
|
|
533
|
+
},
|
|
534
|
+
onResizeMove({ position: nextPosition, size: nextSize, data }) {
|
|
535
|
+
position = nextPosition;
|
|
536
|
+
size = nextSize;
|
|
537
|
+
paint(position, size);
|
|
538
|
+
emitter.emit("resize", data);
|
|
539
|
+
},
|
|
540
|
+
onResizeEnd(data) {
|
|
541
|
+
element.classList.remove("freedom-resizing");
|
|
542
|
+
emitter.emit("resizeend", data);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
const initialResizeHandles = resolveEnabledHandles(options.resizable ?? true);
|
|
546
|
+
if (initialResizeHandles.length > 0) setupResizeHandles(initialResizeHandles);
|
|
547
|
+
const api = {
|
|
548
|
+
id,
|
|
549
|
+
element,
|
|
550
|
+
getPosition() {
|
|
551
|
+
assertAlive("getPosition");
|
|
552
|
+
return { ...position };
|
|
553
|
+
},
|
|
554
|
+
getSize() {
|
|
555
|
+
assertAlive("getSize");
|
|
556
|
+
return { ...size };
|
|
557
|
+
},
|
|
558
|
+
setPosition(point) {
|
|
559
|
+
assertAlive("setPosition");
|
|
560
|
+
position = sanitizePoint(point, "setPosition");
|
|
561
|
+
paint(position);
|
|
562
|
+
},
|
|
563
|
+
setSize(nextSize) {
|
|
564
|
+
assertAlive("setSize");
|
|
565
|
+
size = clampSize(sanitizeSize(nextSize, "setSize"), limits);
|
|
566
|
+
paint(void 0, size);
|
|
567
|
+
},
|
|
568
|
+
focus() {
|
|
569
|
+
assertAlive("focus");
|
|
570
|
+
if (focused) return;
|
|
571
|
+
focused = true;
|
|
572
|
+
element.classList.add("freedom-focused");
|
|
573
|
+
emitter.emit("focus", void 0);
|
|
574
|
+
options.onFocus?.();
|
|
575
|
+
},
|
|
576
|
+
blur() {
|
|
577
|
+
assertAlive("blur");
|
|
578
|
+
if (!focused) return;
|
|
579
|
+
focused = false;
|
|
580
|
+
element.classList.remove("freedom-focused");
|
|
581
|
+
emitter.emit("blur", void 0);
|
|
582
|
+
options.onBlur?.();
|
|
583
|
+
},
|
|
584
|
+
isFocused() {
|
|
585
|
+
assertAlive("isFocused");
|
|
586
|
+
return focused;
|
|
587
|
+
},
|
|
588
|
+
setZIndex(next) {
|
|
589
|
+
assertAlive("setZIndex");
|
|
590
|
+
zIndex = normalizeZIndex(next);
|
|
591
|
+
element.style.zIndex = String(zIndex);
|
|
592
|
+
},
|
|
593
|
+
getZIndex() {
|
|
594
|
+
assertAlive("getZIndex");
|
|
595
|
+
return zIndex;
|
|
596
|
+
},
|
|
597
|
+
enableDrag() {
|
|
598
|
+
assertAlive("enableDrag");
|
|
599
|
+
isDraggable = true;
|
|
600
|
+
},
|
|
601
|
+
disableDrag() {
|
|
602
|
+
assertAlive("disableDrag");
|
|
603
|
+
isDraggable = false;
|
|
604
|
+
if (interactionManager.active === "drag") {
|
|
605
|
+
interactionManager.cancel();
|
|
606
|
+
element.classList.remove("freedom-dragging");
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
enableResize(handles = ALL_HANDLES) {
|
|
610
|
+
assertAlive("enableResize");
|
|
611
|
+
setupResizeHandles(handles);
|
|
612
|
+
},
|
|
613
|
+
disableResize() {
|
|
614
|
+
assertAlive("disableResize");
|
|
615
|
+
if (interactionManager.active === "resize") {
|
|
616
|
+
interactionManager.cancel();
|
|
617
|
+
element.classList.remove("freedom-resizing");
|
|
618
|
+
}
|
|
619
|
+
teardownResizeHandles();
|
|
620
|
+
},
|
|
621
|
+
destroy() {
|
|
622
|
+
if (isDestroyed) return;
|
|
623
|
+
isDestroyed = true;
|
|
624
|
+
interactionManager.destroy();
|
|
625
|
+
teardownResizeHandles();
|
|
626
|
+
scheduler.cancel();
|
|
627
|
+
element.classList.remove("freedom-dragging", "freedom-resizing", "freedom-focused");
|
|
628
|
+
for (const plugin of plugins) plugin.onDestroy?.(pluginContext);
|
|
629
|
+
emitter.emit("destroy", void 0);
|
|
630
|
+
emitter.clear();
|
|
631
|
+
instances.delete(element);
|
|
632
|
+
},
|
|
633
|
+
on(event, handler) {
|
|
634
|
+
assertAlive("on");
|
|
635
|
+
return emitter.on(event, handler);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
instances.set(element, api);
|
|
639
|
+
if (options.onDragStart) api.on("dragstart", options.onDragStart);
|
|
640
|
+
if (options.onDrag) api.on("drag", options.onDrag);
|
|
641
|
+
if (options.onDragEnd) api.on("dragend", options.onDragEnd);
|
|
642
|
+
if (options.onResizeStart) api.on("resizestart", options.onResizeStart);
|
|
643
|
+
if (options.onResize) api.on("resize", options.onResize);
|
|
644
|
+
if (options.onResizeEnd) api.on("resizeend", options.onResizeEnd);
|
|
645
|
+
for (const plugin of plugins) plugin.onInit?.(pluginContext);
|
|
646
|
+
return api;
|
|
647
|
+
}
|
|
648
|
+
function assertHTMLElement(element) {
|
|
649
|
+
const maybeElement = element;
|
|
650
|
+
const isUsableElement = !!maybeElement && typeof maybeElement === "object" && maybeElement.nodeType === 1 && typeof maybeElement.getBoundingClientRect === "function" && !!maybeElement.style && typeof maybeElement.addEventListener === "function" && typeof maybeElement.removeEventListener === "function";
|
|
651
|
+
if (!isUsableElement) {
|
|
652
|
+
throw new TypeError("freedom.window(element) expected a real HTMLElement. Received null, undefined, or a non-element value.");
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function normalizeSizeLimits(options) {
|
|
656
|
+
const minWidth = finiteNonNegative(options.minWidth, 0);
|
|
657
|
+
const minHeight = finiteNonNegative(options.minHeight, 0);
|
|
658
|
+
const maxWidth = finitePositiveOrInfinity(options.maxWidth, Infinity);
|
|
659
|
+
const maxHeight = finitePositiveOrInfinity(options.maxHeight, Infinity);
|
|
660
|
+
return {
|
|
661
|
+
minWidth,
|
|
662
|
+
minHeight,
|
|
663
|
+
maxWidth: Math.max(minWidth, maxWidth),
|
|
664
|
+
maxHeight: Math.max(minHeight, maxHeight)
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function finiteNonNegative(value, fallback) {
|
|
668
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
669
|
+
}
|
|
670
|
+
function finitePositiveOrInfinity(value, fallback) {
|
|
671
|
+
if (value === Infinity) return Infinity;
|
|
672
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
673
|
+
}
|
|
674
|
+
function normalizeZIndex(value) {
|
|
675
|
+
return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
676
|
+
}
|
|
677
|
+
function sanitizePoint(point, source) {
|
|
678
|
+
if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
|
|
679
|
+
throw new TypeError(`freedom.window(): ${source} expected a finite { x, y } point.`);
|
|
680
|
+
}
|
|
681
|
+
return { x: point.x, y: point.y };
|
|
682
|
+
}
|
|
683
|
+
function sanitizeSize(size, source) {
|
|
684
|
+
if (!size || typeof size.width !== "number" || typeof size.height !== "number" || !Number.isFinite(size.width) || !Number.isFinite(size.height)) {
|
|
685
|
+
throw new TypeError(`freedom.window(): ${source} expected a finite { width, height } size.`);
|
|
686
|
+
}
|
|
687
|
+
return { width: Math.max(0, size.width), height: Math.max(0, size.height) };
|
|
688
|
+
}
|
|
689
|
+
function shouldForcePositioning(element, options) {
|
|
690
|
+
const computed = window.getComputedStyle(element).position;
|
|
691
|
+
return Boolean(options.positioning) || computed === "static";
|
|
692
|
+
}
|
|
693
|
+
function resolvePositioning(element, options) {
|
|
694
|
+
if (options.positioning) return options.positioning;
|
|
695
|
+
const computed = window.getComputedStyle(element);
|
|
696
|
+
if (computed.position === "fixed") return "fixed";
|
|
697
|
+
if (computed.position === "absolute") return "absolute";
|
|
698
|
+
if (computed.position === "relative" || computed.position === "sticky") return "relative";
|
|
699
|
+
if (hasAuthoredInset(computed)) return "fixed";
|
|
700
|
+
if (options.initialPosition !== void 0) {
|
|
701
|
+
return options.bounds === "parent" ? "absolute" : "fixed";
|
|
702
|
+
}
|
|
703
|
+
return "relative";
|
|
704
|
+
}
|
|
705
|
+
function hasAuthoredInset(style) {
|
|
706
|
+
return [style.top, style.right, style.bottom, style.left].some((value) => {
|
|
707
|
+
if (!value || value === "auto") return false;
|
|
708
|
+
return value !== "0px" && value !== "0";
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function resolveInitialPosition(initialPosition, element, size, bounds, positioning) {
|
|
712
|
+
if (initialPosition === "center") {
|
|
713
|
+
return centerPosition(element, size, bounds, positioning);
|
|
714
|
+
}
|
|
715
|
+
if (initialPosition) {
|
|
716
|
+
return sanitizePoint(initialPosition, "initialPosition");
|
|
717
|
+
}
|
|
718
|
+
return readRenderedPosition(element, positioning);
|
|
719
|
+
}
|
|
720
|
+
function centerPosition(element, size, bounds, positioning) {
|
|
721
|
+
const box = resolveCenterBox(element, bounds, positioning);
|
|
722
|
+
return {
|
|
723
|
+
x: Math.round(box.x + Math.max(0, box.width - size.width) / 2),
|
|
724
|
+
y: Math.round(box.y + Math.max(0, box.height - size.height) / 2)
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function resolveCenterBox(element, bounds, positioning) {
|
|
728
|
+
if (bounds && typeof bounds === "object") {
|
|
729
|
+
return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height };
|
|
730
|
+
}
|
|
731
|
+
if (bounds === "parent") {
|
|
732
|
+
const parent = element.offsetParent;
|
|
733
|
+
if (parent) return { x: 0, y: 0, width: parent.clientWidth, height: parent.clientHeight };
|
|
734
|
+
}
|
|
735
|
+
if (positioning === "absolute" && bounds !== "viewport") {
|
|
736
|
+
const parent = element.offsetParent;
|
|
737
|
+
if (parent) return { x: 0, y: 0, width: parent.clientWidth, height: parent.clientHeight };
|
|
738
|
+
}
|
|
739
|
+
const root = document.documentElement;
|
|
740
|
+
return {
|
|
741
|
+
x: 0,
|
|
742
|
+
y: 0,
|
|
743
|
+
width: window.innerWidth || root.clientWidth,
|
|
744
|
+
height: window.innerHeight || root.clientHeight
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function readInitialSize(element) {
|
|
748
|
+
const rect = element.getBoundingClientRect();
|
|
749
|
+
return {
|
|
750
|
+
width: rect.width || element.offsetWidth || 0,
|
|
751
|
+
height: rect.height || element.offsetHeight || 0
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function revealIfRequested(element, options) {
|
|
755
|
+
if (options.autoReveal === false) return;
|
|
756
|
+
const visibility = window.getComputedStyle(element).visibility;
|
|
757
|
+
if (visibility === "hidden") {
|
|
758
|
+
element.style.visibility = "visible";
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function resolveDragHandle(element, handleOption) {
|
|
762
|
+
if (handleOption === null) return null;
|
|
763
|
+
if (handleOption === void 0) return element;
|
|
764
|
+
if (typeof handleOption === "string") return element.querySelector(handleOption);
|
|
765
|
+
return handleOption;
|
|
766
|
+
}
|
|
767
|
+
function resolveEnabledHandles(option) {
|
|
768
|
+
if (option === false) return [];
|
|
769
|
+
if (option === true) return [...ALL_HANDLES];
|
|
770
|
+
return normalizeResizeHandles(option);
|
|
771
|
+
}
|
|
772
|
+
function normalizeResizeHandles(handles) {
|
|
773
|
+
const unique = /* @__PURE__ */ new Set();
|
|
774
|
+
for (const handle of handles) {
|
|
775
|
+
if (!ALL_HANDLES.includes(handle)) {
|
|
776
|
+
throw new TypeError(`freedom.window(): invalid resize handle "${String(handle)}".`);
|
|
777
|
+
}
|
|
778
|
+
unique.add(handle);
|
|
779
|
+
}
|
|
780
|
+
return [...unique];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export { createWindow, createWindow as window };
|
|
784
|
+
//# sourceMappingURL=window.js.map
|
|
785
|
+
//# sourceMappingURL=window.js.map
|