svelte-flexiboards 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/flexi-add.svelte +2 -15
- package/dist/components/flexi-add.svelte.d.ts +0 -12
- package/dist/components/flexi-delete.svelte +3 -16
- package/dist/components/flexi-delete.svelte.d.ts +0 -12
- package/dist/components/flexi-grab.svelte +2 -2
- package/dist/components/flexi-target.svelte +8 -19
- package/dist/components/flexi-widget.svelte +2 -2
- package/dist/components/responsive-flexi-board.svelte +83 -0
- package/dist/components/responsive-flexi-board.svelte.d.ts +34 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/system/board/base.svelte.d.ts +15 -0
- package/dist/system/board/controller.svelte.d.ts +26 -4
- package/dist/system/board/controller.svelte.js +237 -28
- package/dist/system/board/types.d.ts +26 -0
- package/dist/system/grid/base.svelte.d.ts +9 -0
- package/dist/system/grid/base.svelte.js +12 -1
- package/dist/system/grid/flow-grid.svelte.js +105 -36
- package/dist/system/grid/free-grid.svelte.d.ts +6 -2
- package/dist/system/grid/free-grid.svelte.js +139 -20
- package/dist/system/misc/deleter.svelte.d.ts +0 -4
- package/dist/system/misc/deleter.svelte.js +1 -6
- package/dist/system/portal.js +0 -1
- package/dist/system/responsive/base.svelte.d.ts +46 -0
- package/dist/system/responsive/base.svelte.js +1 -0
- package/dist/system/responsive/controller.svelte.d.ts +78 -0
- package/dist/system/responsive/controller.svelte.js +264 -0
- package/dist/system/responsive/index.d.ts +16 -0
- package/dist/system/responsive/index.js +36 -0
- package/dist/system/responsive/types.d.ts +56 -0
- package/dist/system/responsive/types.js +1 -0
- package/dist/system/shared/event-bus.d.ts +3 -1
- package/dist/system/shared/utils.svelte.d.ts +2 -0
- package/dist/system/shared/utils.svelte.js +39 -22
- package/dist/system/target/controller.svelte.d.ts +7 -2
- package/dist/system/target/controller.svelte.js +103 -30
- package/dist/system/types.d.ts +13 -2
- package/dist/system/widget/base.svelte.d.ts +40 -1
- package/dist/system/widget/base.svelte.js +84 -2
- package/dist/system/widget/controller.svelte.d.ts +4 -1
- package/dist/system/widget/controller.svelte.js +106 -17
- package/dist/system/widget/events.js +10 -3
- package/dist/system/widget/interpolation-utils.d.ts +14 -0
- package/dist/system/widget/interpolation-utils.js +32 -0
- package/dist/system/widget/interpolator.svelte.d.ts +2 -1
- package/dist/system/widget/interpolator.svelte.js +63 -22
- package/dist/system/widget/triggers.svelte.js +1 -1
- package/dist/system/widget/types.d.ts +51 -6
- package/package.json +1 -1
|
@@ -6,8 +6,9 @@ import { WidgetPointerEventWatcher } from './triggers.svelte.js';
|
|
|
6
6
|
export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
7
7
|
#pointerService = getPointerService();
|
|
8
8
|
// Grabber and resizer tracking
|
|
9
|
-
#grabbers =
|
|
10
|
-
#resizers =
|
|
9
|
+
#grabbers = 0;
|
|
10
|
+
#resizers = 0;
|
|
11
|
+
#disposed = false;
|
|
11
12
|
// Movement interpolation
|
|
12
13
|
interpolator;
|
|
13
14
|
internalTarget = undefined;
|
|
@@ -15,6 +16,16 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
15
16
|
mounted = $state(false);
|
|
16
17
|
#eventBus;
|
|
17
18
|
#unsubscribers = [];
|
|
19
|
+
#lastActionType = null;
|
|
20
|
+
#interpolationAnimationHint = null;
|
|
21
|
+
#type;
|
|
22
|
+
#userProvidedId;
|
|
23
|
+
get type() {
|
|
24
|
+
return this.#type;
|
|
25
|
+
}
|
|
26
|
+
get userProvidedId() {
|
|
27
|
+
return this.#userProvidedId;
|
|
28
|
+
}
|
|
18
29
|
/**
|
|
19
30
|
* The styling to apply to the widget.
|
|
20
31
|
*/
|
|
@@ -38,7 +49,7 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
38
49
|
if (!this.mounted) {
|
|
39
50
|
return '';
|
|
40
51
|
}
|
|
41
|
-
if (!this.
|
|
52
|
+
if (!this.isGrabbable) {
|
|
42
53
|
return '';
|
|
43
54
|
}
|
|
44
55
|
if (this.isGrabbed) {
|
|
@@ -67,10 +78,42 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
67
78
|
return `pointer-events: none; user-select: none; cursor: grabbing; position: absolute; top: ${locationOffsetY}px; left: ${locationOffsetX}px; height: ${height}px; width: ${width}px;`;
|
|
68
79
|
}
|
|
69
80
|
#getResizingWidgetStyle(action) {
|
|
81
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
82
|
+
const parseGap = (value) => {
|
|
83
|
+
if (!value || value === 'normal') {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
const numeric = Number.parseFloat(value);
|
|
87
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
88
|
+
};
|
|
89
|
+
const toPx = (units, unitSize, gapSize) => {
|
|
90
|
+
if (!Number.isFinite(units)) {
|
|
91
|
+
return Infinity;
|
|
92
|
+
}
|
|
93
|
+
return units * unitSize + Math.max(0, units - 1) * gapSize;
|
|
94
|
+
};
|
|
95
|
+
const gridRef = this.internalTarget?.grid?.ref;
|
|
96
|
+
const gridColumns = this.internalTarget?.columns ?? 0;
|
|
97
|
+
const gridRows = this.internalTarget?.rows ?? 0;
|
|
98
|
+
const gridStyle = gridRef && typeof window !== 'undefined' ? window.getComputedStyle(gridRef) : null;
|
|
99
|
+
const columnGapPx = parseGap(gridStyle?.columnGap);
|
|
100
|
+
const rowGapPx = parseGap(gridStyle?.rowGap);
|
|
70
101
|
// Calculate size of one grid unit in pixels
|
|
71
|
-
const
|
|
102
|
+
const fallbackUnitSizeY = (action.capturedHeightPx - Math.max(0, action.initialHeightUnits - 1) * rowGapPx) /
|
|
103
|
+
action.initialHeightUnits;
|
|
72
104
|
// Guard against division by zero if initial width is somehow 0
|
|
73
|
-
const
|
|
105
|
+
const fallbackUnitSizeX = action.initialWidthUnits > 0
|
|
106
|
+
? (action.capturedWidthPx - Math.max(0, action.initialWidthUnits - 1) * columnGapPx) /
|
|
107
|
+
action.initialWidthUnits
|
|
108
|
+
: 1;
|
|
109
|
+
const liveUnitSizeX = gridRef && gridColumns > 0
|
|
110
|
+
? (gridRef.clientWidth - Math.max(0, gridColumns - 1) * columnGapPx) / gridColumns
|
|
111
|
+
: NaN;
|
|
112
|
+
const liveUnitSizeY = gridRef && gridRows > 0
|
|
113
|
+
? (gridRef.clientHeight - Math.max(0, gridRows - 1) * rowGapPx) / gridRows
|
|
114
|
+
: NaN;
|
|
115
|
+
const unitSizeX = Number.isFinite(liveUnitSizeX) && liveUnitSizeX > 0 ? liveUnitSizeX : fallbackUnitSizeX;
|
|
116
|
+
const unitSizeY = Number.isFinite(liveUnitSizeY) && liveUnitSizeY > 0 ? liveUnitSizeY : fallbackUnitSizeY;
|
|
74
117
|
const deltaX = this.#pointerService.position.x - action.offsetX - action.left;
|
|
75
118
|
const deltaY = this.#pointerService.position.y - action.offsetY - action.top;
|
|
76
119
|
// For resizing, top and left should remain fixed at their initial positions.
|
|
@@ -79,19 +122,31 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
79
122
|
// Calculate new dimensions based on resizability
|
|
80
123
|
let height = action.capturedHeightPx;
|
|
81
124
|
let width = action.capturedWidthPx;
|
|
125
|
+
const minWidthPx = toPx(this.minWidth, unitSizeX, columnGapPx);
|
|
126
|
+
const minHeightPx = toPx(this.minHeight, unitSizeY, rowGapPx);
|
|
127
|
+
const gridMaxWidthUnits = this.internalTarget && this.internalTarget.columns > 0
|
|
128
|
+
? Math.max(1, this.internalTarget.columns - this.x)
|
|
129
|
+
: Infinity;
|
|
130
|
+
const gridMaxHeightUnits = this.internalTarget && this.internalTarget.rows > 0
|
|
131
|
+
? Math.max(1, this.internalTarget.rows - this.y)
|
|
132
|
+
: Infinity;
|
|
133
|
+
const configMaxWidthUnits = Number.isFinite(this.maxWidth) ? this.maxWidth : Infinity;
|
|
134
|
+
const configMaxHeightUnits = Number.isFinite(this.maxHeight) ? this.maxHeight : Infinity;
|
|
135
|
+
const maxWidthPx = toPx(Math.min(configMaxWidthUnits, gridMaxWidthUnits), unitSizeX, columnGapPx);
|
|
136
|
+
const maxHeightPx = toPx(Math.min(configMaxHeightUnits, gridMaxHeightUnits), unitSizeY, rowGapPx);
|
|
82
137
|
switch (this.resizability) {
|
|
83
138
|
case 'horizontal':
|
|
84
139
|
// NOTE: Use the pre-calculated deltaX here
|
|
85
|
-
width =
|
|
140
|
+
width = clamp(action.capturedWidthPx + deltaX, minWidthPx, maxWidthPx);
|
|
86
141
|
break;
|
|
87
142
|
case 'vertical':
|
|
88
143
|
// NOTE: Use the pre-calculated deltaY here
|
|
89
|
-
height =
|
|
144
|
+
height = clamp(action.capturedHeightPx + deltaY, minHeightPx, maxHeightPx);
|
|
90
145
|
break;
|
|
91
146
|
case 'both':
|
|
92
147
|
// NOTE: Use the pre-calculated deltaX and deltaY here
|
|
93
|
-
height =
|
|
94
|
-
width =
|
|
148
|
+
height = clamp(action.capturedHeightPx + deltaY, minHeightPx, maxHeightPx);
|
|
149
|
+
width = clamp(action.capturedWidthPx + deltaX, minWidthPx, maxWidthPx);
|
|
95
150
|
break;
|
|
96
151
|
}
|
|
97
152
|
// Return the style string for the absolutely positioned widget
|
|
@@ -113,6 +168,8 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
113
168
|
this.internalTarget = params.target;
|
|
114
169
|
}
|
|
115
170
|
this.provider = params.provider;
|
|
171
|
+
this.#type = params.type;
|
|
172
|
+
this.#userProvidedId = params.config.id;
|
|
116
173
|
// Create the widget's interpolator
|
|
117
174
|
this.interpolator = new WidgetMoveInterpolator(this.provider, this);
|
|
118
175
|
this.#eventBus = getFlexiEventBus();
|
|
@@ -128,6 +185,7 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
128
185
|
if (event.widget !== this) {
|
|
129
186
|
return;
|
|
130
187
|
}
|
|
188
|
+
this.#lastActionType = 'grab';
|
|
131
189
|
// We probably need to wait for the widget to be portalled before we can acquire its focus.
|
|
132
190
|
setTimeout(() => {
|
|
133
191
|
this.ref?.focus();
|
|
@@ -145,6 +203,7 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
145
203
|
if (event.widget !== this) {
|
|
146
204
|
return;
|
|
147
205
|
}
|
|
206
|
+
this.#lastActionType = 'resize';
|
|
148
207
|
// We probably need to wait for the widget to be portalled before we can acquire its focus.
|
|
149
208
|
setTimeout(() => {
|
|
150
209
|
this.ref?.focus();
|
|
@@ -177,15 +236,26 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
177
236
|
* @param height The height of the widget.
|
|
178
237
|
*/
|
|
179
238
|
setBounds(x, y, width, height, interpolate = true) {
|
|
180
|
-
|
|
239
|
+
const previousDimensions = {
|
|
240
|
+
width: this.width,
|
|
241
|
+
height: this.height
|
|
242
|
+
};
|
|
243
|
+
const positionUnchanged = this.x == x && this.y == y && this.width == width && this.height == height;
|
|
244
|
+
if (positionUnchanged) {
|
|
245
|
+
// Still interpolate for drop animations even when position is unchanged
|
|
246
|
+
if (interpolate && this.backingState.currentAction?.action === 'grab') {
|
|
247
|
+
this.#interpolateMove(x, y, this.width, this.height, previousDimensions);
|
|
248
|
+
}
|
|
181
249
|
return;
|
|
182
250
|
}
|
|
251
|
+
const constrainedWidth = Math.max(this.minWidth, Math.min(this.maxWidth, width));
|
|
252
|
+
const constrainedHeight = Math.max(this.minHeight, Math.min(this.maxHeight, height));
|
|
183
253
|
this.backingState.x = x;
|
|
184
254
|
this.backingState.y = y;
|
|
185
|
-
this.backingState.width =
|
|
186
|
-
this.backingState.height =
|
|
255
|
+
this.backingState.width = constrainedWidth;
|
|
256
|
+
this.backingState.height = constrainedHeight;
|
|
187
257
|
if (interpolate) {
|
|
188
|
-
this.#interpolateMove(x, y,
|
|
258
|
+
this.#interpolateMove(x, y, constrainedWidth, constrainedHeight, previousDimensions);
|
|
189
259
|
}
|
|
190
260
|
}
|
|
191
261
|
#getMovementAnimation() {
|
|
@@ -194,11 +264,19 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
194
264
|
return 'drop';
|
|
195
265
|
case 'resize':
|
|
196
266
|
return 'resize';
|
|
197
|
-
default:
|
|
198
|
-
return 'move';
|
|
199
267
|
}
|
|
268
|
+
// If action state has already been released but this update is part of a drop placement,
|
|
269
|
+
// preserve the last interaction animation type for interpolation.
|
|
270
|
+
if (this.backingState.isBeingDropped) {
|
|
271
|
+
return this.#lastActionType === 'resize' ? 'resize' : 'drop';
|
|
272
|
+
}
|
|
273
|
+
// Shadow/dropzone widgets can hint the desired interpolation mode even without an action state.
|
|
274
|
+
if (this.#interpolationAnimationHint) {
|
|
275
|
+
return this.#interpolationAnimationHint;
|
|
276
|
+
}
|
|
277
|
+
return 'move';
|
|
200
278
|
}
|
|
201
|
-
#interpolateMove(x, y, width, height) {
|
|
279
|
+
#interpolateMove(x, y, width, height, previousDimensions) {
|
|
202
280
|
const rect = this.ref?.getBoundingClientRect();
|
|
203
281
|
if (!rect || !this.interpolator) {
|
|
204
282
|
return;
|
|
@@ -213,7 +291,7 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
213
291
|
top: rect.top,
|
|
214
292
|
width: rect.width,
|
|
215
293
|
height: rect.height
|
|
216
|
-
}, this.#getMovementAnimation());
|
|
294
|
+
}, this.#getMovementAnimation(), previousDimensions);
|
|
217
295
|
this.backingState.isBeingDropped = false;
|
|
218
296
|
}
|
|
219
297
|
/**
|
|
@@ -228,6 +306,9 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
228
306
|
* Unregisters a grabber from the widget.
|
|
229
307
|
*/
|
|
230
308
|
removeGrabber() {
|
|
309
|
+
if (this.#disposed) {
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
231
312
|
this.#grabbers--;
|
|
232
313
|
this.backingState.hasGrabbers = this.#grabbers > 0;
|
|
233
314
|
return this.#grabbers;
|
|
@@ -244,6 +325,9 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
244
325
|
* Unregisters a resizer from the widget.
|
|
245
326
|
*/
|
|
246
327
|
removeResizer() {
|
|
328
|
+
if (this.#disposed) {
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
247
331
|
this.#resizers--;
|
|
248
332
|
this.backingState.hasResizers = this.#resizers > 0;
|
|
249
333
|
return this.#resizers;
|
|
@@ -299,6 +383,8 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
299
383
|
* Cleanup method to be called when the widget is destroyed
|
|
300
384
|
*/
|
|
301
385
|
destroy() {
|
|
386
|
+
// Mark as disposed to ignore any deferred cleanup callbacks
|
|
387
|
+
this.#disposed = true;
|
|
302
388
|
// Reset counters
|
|
303
389
|
this.#grabbers = 0;
|
|
304
390
|
this.#resizers = 0;
|
|
@@ -312,4 +398,7 @@ export class InternalFlexiWidgetController extends FlexiWidgetController {
|
|
|
312
398
|
get shouldDrawPlaceholder() {
|
|
313
399
|
return this.interpolator?.active ?? false;
|
|
314
400
|
}
|
|
401
|
+
set interpolationAnimationHint(value) {
|
|
402
|
+
this.#interpolationAnimationHint = value;
|
|
403
|
+
}
|
|
315
404
|
}
|
|
@@ -30,6 +30,8 @@ export function widgetGrabberEvents(widget) {
|
|
|
30
30
|
const grabWatcher = new WidgetPointerEventWatcher(widget, 'grab');
|
|
31
31
|
return {
|
|
32
32
|
onpointerdown: (event) => {
|
|
33
|
+
// Don't propagate to parent widget, preventing double-grab dispatch
|
|
34
|
+
event.stopPropagation();
|
|
33
35
|
// Use the trigger watcher which respects trigger configuration (immediate vs long press)
|
|
34
36
|
grabWatcher.onstartpointerdown(event);
|
|
35
37
|
},
|
|
@@ -40,12 +42,17 @@ export function widgetResizerEvents(widget) {
|
|
|
40
42
|
const eventBus = getFlexiEventBusCtx();
|
|
41
43
|
const board = getInternalFlexiboardCtx();
|
|
42
44
|
const resizeWatcher = new WidgetPointerEventWatcher(widget, 'resize');
|
|
45
|
+
// Don't propagate events upwards, so that it never also triggers a grab action.
|
|
43
46
|
return {
|
|
44
47
|
onpointerdown: (event) => {
|
|
48
|
+
event.stopPropagation();
|
|
45
49
|
// Invoke the trigger watcher which respects trigger configuration (immediate vs long press).
|
|
46
50
|
resizeWatcher.onstartpointerdown(event);
|
|
47
51
|
},
|
|
48
|
-
onkeydown: (event) =>
|
|
52
|
+
onkeydown: (event) => {
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
dispatchKeyDownResize(eventBus, widget, board, event);
|
|
55
|
+
}
|
|
49
56
|
};
|
|
50
57
|
}
|
|
51
58
|
/**
|
|
@@ -55,7 +62,7 @@ export function widgetResizerEvents(widget) {
|
|
|
55
62
|
* @param event The keyboard event.
|
|
56
63
|
*/
|
|
57
64
|
function dispatchKeyDownGrab(eventBus, widget, board, event) {
|
|
58
|
-
if (!widget.
|
|
65
|
+
if (!widget.isGrabbable || !widget.ref || event.key !== 'Enter') {
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
61
68
|
// If an action is already active, do not intercept Enter.
|
|
@@ -80,7 +87,7 @@ function dispatchKeyDownGrab(eventBus, widget, board, event) {
|
|
|
80
87
|
* @param clientY The y-coordinate of the pointer event.
|
|
81
88
|
*/
|
|
82
89
|
function dispatchGrab(eventBus, widget, board, { clientX, clientY }) {
|
|
83
|
-
if (!widget.
|
|
90
|
+
if (!widget.isGrabbable || !widget.ref) {
|
|
84
91
|
return;
|
|
85
92
|
}
|
|
86
93
|
const rect = widget.ref?.getBoundingClientRect();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type InterpolationAnimation = 'move' | 'drop' | 'resize';
|
|
2
|
+
export type InterpolationSize = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
};
|
|
6
|
+
export type PlaceholderMinDimensionLocks = {
|
|
7
|
+
lockMinWidth: boolean;
|
|
8
|
+
lockMinHeight: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Keep min dimensions by default so auto-sized tracks remain stable while the placeholder is hidden.
|
|
12
|
+
* During resize, only unlock min dimensions on axes that actually changed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPlaceholderMinDimensionLocks(animation: InterpolationAnimation, newSize: InterpolationSize, previousSize?: InterpolationSize): PlaceholderMinDimensionLocks;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep min dimensions by default so auto-sized tracks remain stable while the placeholder is hidden.
|
|
3
|
+
* During resize, only unlock min dimensions on axes that actually changed.
|
|
4
|
+
*/
|
|
5
|
+
export function getPlaceholderMinDimensionLocks(animation, newSize, previousSize) {
|
|
6
|
+
if (animation !== 'resize') {
|
|
7
|
+
return {
|
|
8
|
+
lockMinWidth: true,
|
|
9
|
+
lockMinHeight: true
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (!previousSize) {
|
|
13
|
+
// Fallback for legacy call sites: unlock both so shrinking resize animations still work.
|
|
14
|
+
return {
|
|
15
|
+
lockMinWidth: false,
|
|
16
|
+
lockMinHeight: false
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const widthChanged = newSize.width !== previousSize.width;
|
|
20
|
+
const heightChanged = newSize.height !== previousSize.height;
|
|
21
|
+
// Nothing resized (e.g. clamped), keep both mins in place.
|
|
22
|
+
if (!widthChanged && !heightChanged) {
|
|
23
|
+
return {
|
|
24
|
+
lockMinWidth: true,
|
|
25
|
+
lockMinHeight: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
lockMinWidth: !widthChanged,
|
|
30
|
+
lockMinHeight: !heightChanged
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InternalFlexiBoardController } from '../board/controller.svelte.js';
|
|
2
2
|
import type { Position } from '../types.js';
|
|
3
3
|
import type { FlexiWidgetController } from './base.svelte.js';
|
|
4
|
+
import { type InterpolationSize } from './interpolation-utils.js';
|
|
4
5
|
export declare class WidgetMoveInterpolator {
|
|
5
6
|
#private;
|
|
6
7
|
active: boolean;
|
|
@@ -8,7 +9,7 @@ export declare class WidgetMoveInterpolator {
|
|
|
8
9
|
widgetStyle: string;
|
|
9
10
|
placeholderStyle: string;
|
|
10
11
|
constructor(provider: InternalFlexiBoardController, widget: FlexiWidgetController);
|
|
11
|
-
interpolateMove(newDimensions: Dimensions, oldPosition: InterpolationPosition, animation?: WidgetMovementAnimation): void;
|
|
12
|
+
interpolateMove(newDimensions: Dimensions, oldPosition: InterpolationPosition, animation?: WidgetMovementAnimation, previousDimensions?: InterpolationSize): void;
|
|
12
13
|
onPlaceholderMove(rect: DOMRect): void;
|
|
13
14
|
onPlaceholderMount(ref: HTMLElement): () => void;
|
|
14
15
|
onPlaceholderUnmount(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getPlaceholderMinDimensionLocks } from './interpolation-utils.js';
|
|
2
2
|
export class WidgetMoveInterpolator {
|
|
3
3
|
active = $state(false);
|
|
4
4
|
#timeout;
|
|
@@ -12,7 +12,9 @@ export class WidgetMoveInterpolator {
|
|
|
12
12
|
width: 1,
|
|
13
13
|
height: 1,
|
|
14
14
|
heightPx: 0,
|
|
15
|
-
widthPx: 0
|
|
15
|
+
widthPx: 0,
|
|
16
|
+
lockMinWidth: true,
|
|
17
|
+
lockMinHeight: true
|
|
16
18
|
});
|
|
17
19
|
#interpolatedWidgetPosition = $state({
|
|
18
20
|
left: 0,
|
|
@@ -32,7 +34,13 @@ export class WidgetMoveInterpolator {
|
|
|
32
34
|
return `transition: all ${transitionConfig.duration}ms ${transitionConfig.easing}; position: absolute; top: ${this.#interpolatedWidgetPosition.top}px; left: ${this.#interpolatedWidgetPosition.left}px; width: ${this.#interpolatedWidgetPosition.width}px; height: ${this.#interpolatedWidgetPosition.height}px;`;
|
|
33
35
|
});
|
|
34
36
|
placeholderStyle = $derived.by(() => {
|
|
35
|
-
|
|
37
|
+
const minHeight = this.#placeholderPosition.lockMinHeight
|
|
38
|
+
? ` min-height: ${this.#placeholderPosition.heightPx}px;`
|
|
39
|
+
: '';
|
|
40
|
+
const minWidth = this.#placeholderPosition.lockMinWidth
|
|
41
|
+
? ` min-width: ${this.#placeholderPosition.widthPx}px;`
|
|
42
|
+
: '';
|
|
43
|
+
return `grid-column: ${this.#placeholderPosition.x + 1} / span ${this.#placeholderPosition.width}; grid-row: ${this.#placeholderPosition.y + 1} / span ${this.#placeholderPosition.height};${minHeight}${minWidth} visibility: hidden;`;
|
|
36
44
|
});
|
|
37
45
|
constructor(provider, widget) {
|
|
38
46
|
this.#provider = provider;
|
|
@@ -40,7 +48,13 @@ export class WidgetMoveInterpolator {
|
|
|
40
48
|
this.onPlaceholderMount = this.onPlaceholderMount.bind(this);
|
|
41
49
|
this.onPlaceholderUnmount = this.onPlaceholderUnmount.bind(this);
|
|
42
50
|
}
|
|
43
|
-
|
|
51
|
+
#notifyStart() {
|
|
52
|
+
this.#provider?.notifyInterpolationStarted();
|
|
53
|
+
}
|
|
54
|
+
#notifyEnd() {
|
|
55
|
+
this.#provider?.notifyInterpolationEnded();
|
|
56
|
+
}
|
|
57
|
+
interpolateMove(newDimensions, oldPosition, animation = 'move', previousDimensions) {
|
|
44
58
|
const containerRect = this.#containerRef?.getBoundingClientRect();
|
|
45
59
|
if (!containerRect) {
|
|
46
60
|
return;
|
|
@@ -50,29 +64,56 @@ export class WidgetMoveInterpolator {
|
|
|
50
64
|
if (!transitionConfig || !transitionConfig.duration || !transitionConfig.easing) {
|
|
51
65
|
return;
|
|
52
66
|
}
|
|
53
|
-
|
|
54
|
-
clearTimeout(this.#timeout);
|
|
55
|
-
}
|
|
56
|
-
this.active = true;
|
|
57
|
-
this.#animation = animation;
|
|
58
|
-
this.#placeholderPosition = {
|
|
59
|
-
x: newDimensions.x,
|
|
60
|
-
y: newDimensions.y,
|
|
67
|
+
const minDimensionLocks = getPlaceholderMinDimensionLocks(animation, {
|
|
61
68
|
width: newDimensions.width,
|
|
62
|
-
height: newDimensions.height
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
height: newDimensions.height
|
|
70
|
+
}, previousDimensions);
|
|
71
|
+
const isInterruption = this.active;
|
|
72
|
+
clearTimeout(this.#timeout);
|
|
73
|
+
if (isInterruption) {
|
|
74
|
+
// INTERRUPTION PATH - only update target, keep transition flowing
|
|
75
|
+
// Don't change #animation to preserve CSS transition property
|
|
76
|
+
// Don't reset #interpolatedWidgetPosition
|
|
77
|
+
this.#placeholderPosition = {
|
|
78
|
+
x: newDimensions.x,
|
|
79
|
+
y: newDimensions.y,
|
|
80
|
+
width: newDimensions.width,
|
|
81
|
+
height: newDimensions.height,
|
|
82
|
+
heightPx: this.#interpolatedWidgetPosition.height,
|
|
83
|
+
widthPx: this.#interpolatedWidgetPosition.width,
|
|
84
|
+
lockMinWidth: minDimensionLocks.lockMinWidth,
|
|
85
|
+
lockMinHeight: minDimensionLocks.lockMinHeight
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// INITIAL MOVE PATH - set up starting position, then animate
|
|
90
|
+
this.#inInitialFrame = true; // Disable CSS transition for initial position
|
|
91
|
+
this.active = true;
|
|
92
|
+
this.#animation = animation;
|
|
93
|
+
this.#notifyStart();
|
|
94
|
+
this.#placeholderPosition = {
|
|
95
|
+
x: newDimensions.x,
|
|
96
|
+
y: newDimensions.y,
|
|
97
|
+
width: newDimensions.width,
|
|
98
|
+
height: newDimensions.height,
|
|
99
|
+
heightPx: oldPosition.height,
|
|
100
|
+
widthPx: oldPosition.width,
|
|
101
|
+
lockMinWidth: minDimensionLocks.lockMinWidth,
|
|
102
|
+
lockMinHeight: minDimensionLocks.lockMinHeight
|
|
103
|
+
};
|
|
104
|
+
this.#interpolatedWidgetPosition.top =
|
|
105
|
+
oldPosition.top - containerRect.top + (this.#containerRef?.scrollTop ?? 0);
|
|
106
|
+
this.#interpolatedWidgetPosition.left =
|
|
107
|
+
oldPosition.left - containerRect.left + (this.#containerRef?.scrollLeft ?? 0);
|
|
108
|
+
this.#interpolatedWidgetPosition.width = oldPosition.width;
|
|
109
|
+
this.#interpolatedWidgetPosition.height = oldPosition.height;
|
|
110
|
+
}
|
|
111
|
+
// Reset timeout for both paths
|
|
72
112
|
requestAnimationFrame(() => {
|
|
73
113
|
this.#timeout = setTimeout(() => {
|
|
74
114
|
this.active = false;
|
|
75
115
|
this.#animation = 'move';
|
|
116
|
+
this.#notifyEnd();
|
|
76
117
|
}, transitionConfig.duration);
|
|
77
118
|
});
|
|
78
119
|
}
|
|
@@ -2,7 +2,7 @@ import type { ClassValue } from 'svelte/elements';
|
|
|
2
2
|
import type { FlexiWidgetController } from './base.svelte.js';
|
|
3
3
|
import type { Component, Snippet } from 'svelte';
|
|
4
4
|
import { type PointerTriggerCondition } from './triggers.svelte.js';
|
|
5
|
-
import type { WidgetAction, WidgetResizability } from '../types.js';
|
|
5
|
+
import type { WidgetAction, WidgetDraggability, WidgetResizability } from '../types.js';
|
|
6
6
|
import type { InternalFlexiTargetController } from '../target/controller.svelte.js';
|
|
7
7
|
import type { InternalFlexiBoardController } from '../board/controller.svelte.js';
|
|
8
8
|
export type FlexiWidgetChildrenSnippetParameters = {
|
|
@@ -24,18 +24,25 @@ export type FlexiWidgetTriggerConfiguration = Record<string, PointerTriggerCondi
|
|
|
24
24
|
export type FlexiWidgetDefaults = {
|
|
25
25
|
/**
|
|
26
26
|
* Whether the widget is draggable.
|
|
27
|
+
* @deprecated Prefer the use of `draggability` instead for finer control. When `true`, `draggability = 'full'`, when `false`, `draggability = 'none'`.
|
|
27
28
|
*/
|
|
28
29
|
draggable?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The draggability of the widget.
|
|
32
|
+
*/
|
|
33
|
+
draggability?: WidgetDraggability;
|
|
29
34
|
/**
|
|
30
35
|
* The resizability of the widget.
|
|
31
36
|
*/
|
|
32
37
|
resizability?: WidgetResizability;
|
|
33
38
|
/**
|
|
34
39
|
* The width of the widget in units.
|
|
40
|
+
* @deprecated This property does not work and will be removed in the next version.
|
|
35
41
|
*/
|
|
36
42
|
width?: number;
|
|
37
43
|
/**
|
|
38
44
|
* The height of the widget in units.
|
|
45
|
+
* @deprecated This property does not work and will be removed in the next version.
|
|
39
46
|
*/
|
|
40
47
|
height?: number;
|
|
41
48
|
/**
|
|
@@ -68,10 +75,30 @@ export type FlexiWidgetDefaults = {
|
|
|
68
75
|
* on the widget. E.g. a long press.
|
|
69
76
|
*/
|
|
70
77
|
resizeTrigger?: FlexiWidgetTriggerConfiguration;
|
|
78
|
+
/**
|
|
79
|
+
* The minimum width of the widget in units. Defaults to 1, cannot be less than 1.
|
|
80
|
+
*/
|
|
81
|
+
minWidth?: number;
|
|
82
|
+
/**
|
|
83
|
+
* The minimum height of the widget in units. Defaults to 1, cannot be less than 1.
|
|
84
|
+
*/
|
|
85
|
+
minHeight?: number;
|
|
86
|
+
/**
|
|
87
|
+
* The maximum width of the widget in units. Defaults to Infinity, cannot be less than 1.
|
|
88
|
+
*/
|
|
89
|
+
maxWidth?: number;
|
|
90
|
+
/**
|
|
91
|
+
* The maximum height of the widget in units. Defaults to Infinity, cannot be less than 1.
|
|
92
|
+
*/
|
|
93
|
+
maxHeight?: number;
|
|
71
94
|
};
|
|
72
95
|
export type FlexiWidgetConfiguration = FlexiWidgetDefaults & {
|
|
96
|
+
id?: string;
|
|
97
|
+
type?: string;
|
|
73
98
|
x?: number;
|
|
74
99
|
y?: number;
|
|
100
|
+
width?: number;
|
|
101
|
+
height?: number;
|
|
75
102
|
metadata?: Record<string, any>;
|
|
76
103
|
};
|
|
77
104
|
export type FlexiWidgetState = {
|
|
@@ -82,10 +109,6 @@ export type FlexiWidgetState = {
|
|
|
82
109
|
y: number;
|
|
83
110
|
};
|
|
84
111
|
export type FlexiWidgetDerivedConfiguration = {
|
|
85
|
-
/**
|
|
86
|
-
* The name of the widget, which can be used to identify it in exported layouts.
|
|
87
|
-
*/
|
|
88
|
-
name?: string;
|
|
89
112
|
/**
|
|
90
113
|
* The component that is rendered by this item. This is optional if a snippet is provided.
|
|
91
114
|
*/
|
|
@@ -103,9 +126,14 @@ export type FlexiWidgetDerivedConfiguration = {
|
|
|
103
126
|
*/
|
|
104
127
|
resizability: WidgetResizability;
|
|
105
128
|
/**
|
|
106
|
-
* Whether the
|
|
129
|
+
* Whether the widget is draggable.
|
|
130
|
+
* @deprecated Prefer the use of `draggability` instead for finer control. When `true`, `draggability = 'full'`, when `false`, `draggability = 'none'`.
|
|
107
131
|
*/
|
|
108
132
|
draggable: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* The draggability of the widget.
|
|
135
|
+
*/
|
|
136
|
+
draggability: WidgetDraggability;
|
|
109
137
|
/**
|
|
110
138
|
* The class name that is applied to this widget.
|
|
111
139
|
*/
|
|
@@ -128,11 +156,28 @@ export type FlexiWidgetDerivedConfiguration = {
|
|
|
128
156
|
* The transition configuration for this widget.
|
|
129
157
|
*/
|
|
130
158
|
transition: FlexiWidgetTransitionConfiguration;
|
|
159
|
+
/**
|
|
160
|
+
* The minimum width of the widget in units. Defaults to 1, cannot be less than 1.
|
|
161
|
+
*/
|
|
162
|
+
minWidth: number;
|
|
163
|
+
/**
|
|
164
|
+
* The minimum height of the widget in units. Defaults to 1, cannot be less than 1.
|
|
165
|
+
*/
|
|
166
|
+
minHeight: number;
|
|
167
|
+
/**
|
|
168
|
+
* The maximum width of the widget in units. Defaults to Infinity, cannot be less than 1.
|
|
169
|
+
*/
|
|
170
|
+
maxWidth: number;
|
|
171
|
+
/**
|
|
172
|
+
* The maximum height of the widget in units. Defaults to Infinity, cannot be less than 1.
|
|
173
|
+
*/
|
|
174
|
+
maxHeight: number;
|
|
131
175
|
};
|
|
132
176
|
export type FlexiWidgetConstructorParams = {
|
|
133
177
|
config: FlexiWidgetConfiguration;
|
|
134
178
|
provider: InternalFlexiBoardController;
|
|
135
179
|
target?: InternalFlexiTargetController;
|
|
180
|
+
type?: string;
|
|
136
181
|
isShadow?: boolean;
|
|
137
182
|
};
|
|
138
183
|
export declare const defaultTriggerConfig: FlexiWidgetTriggerConfiguration;
|