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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { FlexiLayout } from '../board/types.js';
|
|
2
|
+
import type { ResponsiveFlexiBoardController } from './base.svelte.js';
|
|
3
|
+
import type { ResponsiveFlexiBoardConfiguration, ResponsiveFlexiLayout } from './types.js';
|
|
4
|
+
import type { ResponsiveFlexiBoardProps } from '../../components/responsive-flexi-board.svelte';
|
|
5
|
+
export declare class InternalResponsiveFlexiBoardController implements ResponsiveFlexiBoardController {
|
|
6
|
+
#private;
|
|
7
|
+
config?: ResponsiveFlexiBoardConfiguration;
|
|
8
|
+
/**
|
|
9
|
+
* The currently active breakpoint key.
|
|
10
|
+
* Determined by checking MediaQuery matches in descending order.
|
|
11
|
+
* Falls back to 'default' if no breakpoint matches.
|
|
12
|
+
*/
|
|
13
|
+
currentBreakpoint: string;
|
|
14
|
+
constructor(props: ResponsiveFlexiBoardProps);
|
|
15
|
+
/**
|
|
16
|
+
* Gets the layout for the current breakpoint.
|
|
17
|
+
* Falls back to 'default' if no layout exists for the current breakpoint.
|
|
18
|
+
*/
|
|
19
|
+
getLayoutForCurrentBreakpoint(): FlexiLayout | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Updates the layout for the current breakpoint.
|
|
22
|
+
* Called when the active FlexiBoard's layout changes.
|
|
23
|
+
*/
|
|
24
|
+
setLayoutForCurrentBreakpoint(layout: FlexiLayout): void;
|
|
25
|
+
/**
|
|
26
|
+
* Gets the layout for a specific breakpoint.
|
|
27
|
+
*/
|
|
28
|
+
getLayoutForBreakpoint(breakpoint: string): FlexiLayout | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Sets the layout for a specific breakpoint.
|
|
31
|
+
*/
|
|
32
|
+
setLayoutForBreakpoint(breakpoint: string, layout: FlexiLayout): void;
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a layout exists for a specific breakpoint.
|
|
35
|
+
*/
|
|
36
|
+
hasLayoutForBreakpoint(breakpoint: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Imports all responsive layouts.
|
|
39
|
+
* Replaces any existing stored layouts.
|
|
40
|
+
*/
|
|
41
|
+
importLayout(layout: ResponsiveFlexiLayout): void;
|
|
42
|
+
/**
|
|
43
|
+
* Exports all responsive layouts.
|
|
44
|
+
* Returns a copy of the stored layouts.
|
|
45
|
+
*/
|
|
46
|
+
exportLayout(): ResponsiveFlexiLayout;
|
|
47
|
+
/**
|
|
48
|
+
* Returns all breakpoint keys that have stored layouts.
|
|
49
|
+
*/
|
|
50
|
+
get definedBreakpoints(): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Returns all configured breakpoint keys (from config).
|
|
53
|
+
*/
|
|
54
|
+
get configuredBreakpoints(): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Returns the sorted breakpoints (largest first, excluding 'default').
|
|
57
|
+
*/
|
|
58
|
+
get sortedBreakpoints(): [string, number][];
|
|
59
|
+
/**
|
|
60
|
+
* Called when the responsive board is ready to load layouts.
|
|
61
|
+
* Triggers initial layout loading from the `loadLayouts` config.
|
|
62
|
+
*/
|
|
63
|
+
oninitialloadcomplete(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the responsive board has completed initial loading.
|
|
66
|
+
*/
|
|
67
|
+
get ready(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether layouts have been imported via importLayout() or received from child boards.
|
|
70
|
+
* Child boards check this to know whether to auto-load from stored layouts
|
|
71
|
+
* or use their loadLayout callback.
|
|
72
|
+
*/
|
|
73
|
+
get hasStoredLayouts(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Cleanup method to be called when the responsive board is destroyed.
|
|
76
|
+
*/
|
|
77
|
+
destroy(): void;
|
|
78
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { MediaQuery, SvelteMap } from 'svelte/reactivity';
|
|
2
|
+
import { getFlexiEventBus } from '../shared/event-bus.js';
|
|
3
|
+
const DEFAULT_BREAKPOINT = 'default';
|
|
4
|
+
export class InternalResponsiveFlexiBoardController {
|
|
5
|
+
#rawProps = $state(undefined);
|
|
6
|
+
config = $derived(this.#rawProps?.config);
|
|
7
|
+
/**
|
|
8
|
+
* Stored layouts for all breakpoints.
|
|
9
|
+
* This is the source of truth - active board syncs to/from this.
|
|
10
|
+
*/
|
|
11
|
+
#storedLayouts = $state({});
|
|
12
|
+
/**
|
|
13
|
+
* Whether initial layout loading has completed.
|
|
14
|
+
*/
|
|
15
|
+
#ready = false;
|
|
16
|
+
/**
|
|
17
|
+
* Debounce timer for layout change callbacks.
|
|
18
|
+
*/
|
|
19
|
+
#layoutChangeTimeout = null;
|
|
20
|
+
#layoutChangeDebounceMs = 150;
|
|
21
|
+
/**
|
|
22
|
+
* Whether initial layouts have been imported via importLayout().
|
|
23
|
+
* Used by child boards to determine whether to load from stored layouts or loadLayout callback.
|
|
24
|
+
*/
|
|
25
|
+
#hasStoredLayouts = false;
|
|
26
|
+
/**
|
|
27
|
+
* Event bus for communication between controllers.
|
|
28
|
+
*/
|
|
29
|
+
#eventBus;
|
|
30
|
+
/**
|
|
31
|
+
* Cleanup functions for event subscriptions.
|
|
32
|
+
*/
|
|
33
|
+
#unsubscribers = [];
|
|
34
|
+
/**
|
|
35
|
+
* MediaQuery instances for each breakpoint.
|
|
36
|
+
* Created lazily when breakpoints config changes.
|
|
37
|
+
*/
|
|
38
|
+
#mediaQueries = new SvelteMap();
|
|
39
|
+
/**
|
|
40
|
+
* Breakpoints sorted in descending order by min-width.
|
|
41
|
+
* Largest breakpoint first, so we match the largest applicable one.
|
|
42
|
+
*/
|
|
43
|
+
#sortedBreakpoints = $derived.by(() => {
|
|
44
|
+
const breakpoints = this.config?.breakpoints ?? {};
|
|
45
|
+
return Object.entries(breakpoints)
|
|
46
|
+
.filter(([key]) => key !== DEFAULT_BREAKPOINT)
|
|
47
|
+
.sort((a, b) => b[1] - a[1]);
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* The currently active breakpoint key.
|
|
51
|
+
* Determined by checking MediaQuery matches in descending order.
|
|
52
|
+
* Falls back to 'default' if no breakpoint matches.
|
|
53
|
+
*/
|
|
54
|
+
currentBreakpoint = $derived.by(() => {
|
|
55
|
+
// Check breakpoints in descending order (largest first)
|
|
56
|
+
for (const [key] of this.#sortedBreakpoints) {
|
|
57
|
+
const query = this.#mediaQueries.get(key);
|
|
58
|
+
if (query?.current) {
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return DEFAULT_BREAKPOINT;
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Previous breakpoint for detecting changes.
|
|
66
|
+
*/
|
|
67
|
+
#previousBreakpoint = DEFAULT_BREAKPOINT;
|
|
68
|
+
constructor(props) {
|
|
69
|
+
this.#rawProps = props;
|
|
70
|
+
this.#eventBus = getFlexiEventBus();
|
|
71
|
+
// Subscribe to board layout changes
|
|
72
|
+
this.#unsubscribers.push(this.#eventBus.subscribe('board:layoutchange', this.#onBoardLayoutChange.bind(this)));
|
|
73
|
+
// Initialize media queries when breakpoints config changes
|
|
74
|
+
$effect(() => {
|
|
75
|
+
this.#initializeMediaQueries();
|
|
76
|
+
});
|
|
77
|
+
// Detect and handle breakpoint changes
|
|
78
|
+
$effect(() => {
|
|
79
|
+
const current = this.currentBreakpoint;
|
|
80
|
+
const previous = this.#previousBreakpoint;
|
|
81
|
+
if (previous !== current) {
|
|
82
|
+
this.#onBreakpointChange(previous, current);
|
|
83
|
+
this.#previousBreakpoint = current;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Handles layout change events from child boards.
|
|
89
|
+
*/
|
|
90
|
+
#onBoardLayoutChange(event) {
|
|
91
|
+
// Only handle events from boards under our control
|
|
92
|
+
if (event.board.responsiveController !== this) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Store the layout for this breakpoint
|
|
96
|
+
if (event.breakpoint) {
|
|
97
|
+
this.#storedLayouts[event.breakpoint] = event.layout;
|
|
98
|
+
this.#hasStoredLayouts = true;
|
|
99
|
+
this.#notifyLayoutChange();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Creates MediaQuery instances for each configured breakpoint.
|
|
104
|
+
*/
|
|
105
|
+
#initializeMediaQueries() {
|
|
106
|
+
const breakpoints = this.config?.breakpoints ?? {};
|
|
107
|
+
// Clear existing queries
|
|
108
|
+
this.#mediaQueries.clear();
|
|
109
|
+
// Create min-width queries for each breakpoint (except 'default')
|
|
110
|
+
for (const [key, minWidth] of Object.entries(breakpoints)) {
|
|
111
|
+
if (key === DEFAULT_BREAKPOINT) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
this.#mediaQueries.set(key, new MediaQuery(`(min-width: ${minWidth}px)`, false));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Called when the active breakpoint changes.
|
|
119
|
+
*/
|
|
120
|
+
#onBreakpointChange(oldBreakpoint, newBreakpoint) {
|
|
121
|
+
this.config?.onBreakpointChange?.(newBreakpoint, oldBreakpoint);
|
|
122
|
+
}
|
|
123
|
+
// =========================================================================
|
|
124
|
+
// Layout access for the active breakpoint (used by component)
|
|
125
|
+
// =========================================================================
|
|
126
|
+
/**
|
|
127
|
+
* Gets the layout for the current breakpoint.
|
|
128
|
+
* Falls back to 'default' if no layout exists for the current breakpoint.
|
|
129
|
+
*/
|
|
130
|
+
getLayoutForCurrentBreakpoint() {
|
|
131
|
+
return this.#storedLayouts[this.currentBreakpoint] ?? this.#storedLayouts[DEFAULT_BREAKPOINT];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Updates the layout for the current breakpoint.
|
|
135
|
+
* Called when the active FlexiBoard's layout changes.
|
|
136
|
+
*/
|
|
137
|
+
setLayoutForCurrentBreakpoint(layout) {
|
|
138
|
+
this.#storedLayouts[this.currentBreakpoint] = layout;
|
|
139
|
+
this.#notifyLayoutChange();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Debounced notification of layout changes.
|
|
143
|
+
*/
|
|
144
|
+
#notifyLayoutChange() {
|
|
145
|
+
if (this.#layoutChangeTimeout) {
|
|
146
|
+
clearTimeout(this.#layoutChangeTimeout);
|
|
147
|
+
}
|
|
148
|
+
this.#layoutChangeTimeout = setTimeout(() => {
|
|
149
|
+
this.#layoutChangeTimeout = null;
|
|
150
|
+
this.config?.onLayoutsChange?.(this.exportLayout());
|
|
151
|
+
}, this.#layoutChangeDebounceMs);
|
|
152
|
+
}
|
|
153
|
+
// =========================================================================
|
|
154
|
+
// Per-breakpoint layout access
|
|
155
|
+
// =========================================================================
|
|
156
|
+
/**
|
|
157
|
+
* Gets the layout for a specific breakpoint.
|
|
158
|
+
*/
|
|
159
|
+
getLayoutForBreakpoint(breakpoint) {
|
|
160
|
+
return this.#storedLayouts[breakpoint];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Sets the layout for a specific breakpoint.
|
|
164
|
+
*/
|
|
165
|
+
setLayoutForBreakpoint(breakpoint, layout) {
|
|
166
|
+
this.#storedLayouts[breakpoint] = layout;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Checks if a layout exists for a specific breakpoint.
|
|
170
|
+
*/
|
|
171
|
+
hasLayoutForBreakpoint(breakpoint) {
|
|
172
|
+
return breakpoint in this.#storedLayouts;
|
|
173
|
+
}
|
|
174
|
+
// =========================================================================
|
|
175
|
+
// Import/Export (ResponsiveFlexiBoardController interface)
|
|
176
|
+
// =========================================================================
|
|
177
|
+
/**
|
|
178
|
+
* Imports all responsive layouts.
|
|
179
|
+
* Replaces any existing stored layouts.
|
|
180
|
+
*/
|
|
181
|
+
importLayout(layout) {
|
|
182
|
+
this.#storedLayouts = { ...layout };
|
|
183
|
+
this.#hasStoredLayouts = true;
|
|
184
|
+
// Notify child boards to reload their layout from the stored layouts
|
|
185
|
+
this.#eventBus.dispatch('responsive:layoutimport', {
|
|
186
|
+
responsiveController: this
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Exports all responsive layouts.
|
|
191
|
+
* Returns a copy of the stored layouts.
|
|
192
|
+
*/
|
|
193
|
+
exportLayout() {
|
|
194
|
+
return { ...this.#storedLayouts };
|
|
195
|
+
}
|
|
196
|
+
// =========================================================================
|
|
197
|
+
// Utility getters
|
|
198
|
+
// =========================================================================
|
|
199
|
+
/**
|
|
200
|
+
* Returns all breakpoint keys that have stored layouts.
|
|
201
|
+
*/
|
|
202
|
+
get definedBreakpoints() {
|
|
203
|
+
return Object.keys(this.#storedLayouts);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Returns all configured breakpoint keys (from config).
|
|
207
|
+
*/
|
|
208
|
+
get configuredBreakpoints() {
|
|
209
|
+
return Object.keys(this.config?.breakpoints ?? {});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Returns the sorted breakpoints (largest first, excluding 'default').
|
|
213
|
+
*/
|
|
214
|
+
get sortedBreakpoints() {
|
|
215
|
+
return this.#sortedBreakpoints;
|
|
216
|
+
}
|
|
217
|
+
// =========================================================================
|
|
218
|
+
// Lifecycle
|
|
219
|
+
// =========================================================================
|
|
220
|
+
/**
|
|
221
|
+
* Called when the responsive board is ready to load layouts.
|
|
222
|
+
* Triggers initial layout loading from the `loadLayouts` config.
|
|
223
|
+
*/
|
|
224
|
+
oninitialloadcomplete() {
|
|
225
|
+
if (this.#ready) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.#ready = true;
|
|
229
|
+
const loadLayoutsFn = this.config?.loadLayouts;
|
|
230
|
+
if (loadLayoutsFn) {
|
|
231
|
+
const layouts = loadLayoutsFn();
|
|
232
|
+
if (layouts) {
|
|
233
|
+
this.importLayout(layouts);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Whether the responsive board has completed initial loading.
|
|
239
|
+
*/
|
|
240
|
+
get ready() {
|
|
241
|
+
return this.#ready;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Whether layouts have been imported via importLayout() or received from child boards.
|
|
245
|
+
* Child boards check this to know whether to auto-load from stored layouts
|
|
246
|
+
* or use their loadLayout callback.
|
|
247
|
+
*/
|
|
248
|
+
get hasStoredLayouts() {
|
|
249
|
+
return this.#hasStoredLayouts;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Cleanup method to be called when the responsive board is destroyed.
|
|
253
|
+
*/
|
|
254
|
+
destroy() {
|
|
255
|
+
this.#mediaQueries.clear();
|
|
256
|
+
// Clean up event subscriptions
|
|
257
|
+
this.#unsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
258
|
+
this.#unsubscribers = [];
|
|
259
|
+
if (this.#layoutChangeTimeout) {
|
|
260
|
+
clearTimeout(this.#layoutChangeTimeout);
|
|
261
|
+
this.#layoutChangeTimeout = null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ResponsiveFlexiBoardController } from './base.svelte.js';
|
|
2
|
+
import { InternalResponsiveFlexiBoardController } from './controller.svelte.js';
|
|
3
|
+
import type { ResponsiveFlexiBoardProps } from '../../components/responsive-flexi-board.svelte';
|
|
4
|
+
import type { ResponsiveFlexiBoardConfiguration, ResponsiveFlexiLayout } from './types.js';
|
|
5
|
+
export declare function responsiveflexiboard(props: ResponsiveFlexiBoardProps): InternalResponsiveFlexiBoardController;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a responsive board context is available.
|
|
8
|
+
* @returns Whether a responsive board context is available.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hasInternalResponsiveFlexiboardCtx(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Gets the current {@link ResponsiveFlexiBoardController} instance, if any.
|
|
13
|
+
* Throws an error if no responsive board is found.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getResponsiveFlexiboardCtx(): ResponsiveFlexiBoardController;
|
|
16
|
+
export { type ResponsiveFlexiBoardController, type ResponsiveFlexiBoardConfiguration, type ResponsiveFlexiLayout };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
import { InternalResponsiveFlexiBoardController } from './controller.svelte.js';
|
|
3
|
+
const contextKey = Symbol('responsive-flexiboard');
|
|
4
|
+
export function responsiveflexiboard(props) {
|
|
5
|
+
const board = new InternalResponsiveFlexiBoardController(props);
|
|
6
|
+
setContext(contextKey, board);
|
|
7
|
+
return board;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Gets the current {@link InternalResponsiveFlexiBoardController} instance, if any.
|
|
11
|
+
* Throws an error if no responsive board is found.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export function getInternalResponsiveFlexiboardCtx() {
|
|
15
|
+
const board = getContext(contextKey);
|
|
16
|
+
if (!board) {
|
|
17
|
+
throw new Error('Cannot get ResponsiveFlexiBoard context outside of a registered board. Ensure that responsiveflexiboard() (or <ResponsiveFlexiBoard>) is called.');
|
|
18
|
+
}
|
|
19
|
+
return board;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Checks if a responsive board context is available.
|
|
23
|
+
* @returns Whether a responsive board context is available.
|
|
24
|
+
*/
|
|
25
|
+
export function hasInternalResponsiveFlexiboardCtx() {
|
|
26
|
+
return !!getContext(contextKey);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Gets the current {@link ResponsiveFlexiBoardController} instance, if any.
|
|
30
|
+
* Throws an error if no responsive board is found.
|
|
31
|
+
*/
|
|
32
|
+
export function getResponsiveFlexiboardCtx() {
|
|
33
|
+
return getInternalResponsiveFlexiboardCtx();
|
|
34
|
+
}
|
|
35
|
+
/* Exports to go to root index.ts */
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { FlexiLayout } from "../board/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A responsive layout is a map of breakpoint keys to FlexiBoard layouts.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const layout: ResponsiveFlexiLayout = {
|
|
8
|
+
* lg: { "target-0": [{ type: "widget", x: 0, y: 0, width: 4, height: 2 }] },
|
|
9
|
+
* md: { "target-0": [{ type: "widget", x: 0, y: 0, width: 6, height: 2 }] },
|
|
10
|
+
* default: { "target-0": [{ type: "widget", x: 0, y: 0, width: 12, height: 2 }] },
|
|
11
|
+
* };
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export type ResponsiveFlexiLayout = {
|
|
15
|
+
[breakpoint: string]: FlexiLayout;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Callback fired when any breakpoint's layout changes.
|
|
19
|
+
*/
|
|
20
|
+
export type ResponsiveFlexiLayoutChangeFn = (layouts: ResponsiveFlexiLayout) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Function to load initial layouts for all breakpoints.
|
|
23
|
+
*/
|
|
24
|
+
export type ResponsiveFlexiLoadLayoutFn = () => ResponsiveFlexiLayout | undefined;
|
|
25
|
+
export type ResponsiveFlexiBoardConfiguration = {
|
|
26
|
+
/**
|
|
27
|
+
* Breakpoint definitions mapping breakpoint keys to minimum viewport widths (in pixels).
|
|
28
|
+
* Breakpoints are evaluated in descending order - the largest matching breakpoint wins.
|
|
29
|
+
* Use 'default' as the fallback when no breakpoint matches.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* breakpoints: {
|
|
34
|
+
* lg: 1200, // >= 1200px
|
|
35
|
+
* md: 900, // >= 900px
|
|
36
|
+
* sm: 600, // >= 600px
|
|
37
|
+
* // 'default' is implicit for < 600px
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
breakpoints?: Record<string, number>;
|
|
42
|
+
/**
|
|
43
|
+
* Callback fired when the active breakpoint changes.
|
|
44
|
+
*/
|
|
45
|
+
onBreakpointChange?: (newBreakpoint: string, oldBreakpoint: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Callback fired when any layout changes (widget moved, resized, added, or removed).
|
|
48
|
+
* Receives all breakpoint layouts, including the updated current one.
|
|
49
|
+
*/
|
|
50
|
+
onLayoutsChange?: ResponsiveFlexiLayoutChangeFn;
|
|
51
|
+
/**
|
|
52
|
+
* Function to load initial layouts on mount.
|
|
53
|
+
* Called once when the responsive board is ready.
|
|
54
|
+
*/
|
|
55
|
+
loadLayouts?: ResponsiveFlexiLoadLayoutFn;
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdderWidgetReadyEvent, PointerMovedEvent, TargetEvent, WidgetDroppedEvent, WidgetEvent, WidgetGrabbedEvent, WidgetResizingEvent } from '../types.js';
|
|
1
|
+
import type { AdderWidgetReadyEvent, BoardLayoutChangeEvent, PointerMovedEvent, ResponsiveLayoutImportEvent, TargetEvent, WidgetDroppedEvent, WidgetEvent, WidgetGrabbedEvent, WidgetResizingEvent } from '../types.js';
|
|
2
2
|
export interface EventMap {
|
|
3
3
|
'widget:grabbed': WidgetGrabbedEvent;
|
|
4
4
|
'widget:resizing': WidgetResizingEvent;
|
|
@@ -12,6 +12,8 @@ export interface EventMap {
|
|
|
12
12
|
'widget:leavetarget': WidgetEvent;
|
|
13
13
|
'adder:widgetready': AdderWidgetReadyEvent;
|
|
14
14
|
'pointer:moved': PointerMovedEvent;
|
|
15
|
+
'board:layoutchange': BoardLayoutChangeEvent;
|
|
16
|
+
'responsive:layoutimport': ResponsiveLayoutImportEvent;
|
|
15
17
|
}
|
|
16
18
|
type EventListener<T> = (data: T) => void;
|
|
17
19
|
export declare class FlexiEventBus {
|
|
@@ -60,6 +60,8 @@ export declare class GridDimensionTracker {
|
|
|
60
60
|
refreshScrollListeners(): void;
|
|
61
61
|
getCellFromPointerPosition(clientX: number, clientY: number): CellPosition | null;
|
|
62
62
|
}
|
|
63
|
+
export declare function contentSize(axisCoordinates: number[], gap: number): number;
|
|
64
|
+
export declare function findCell(pointerLocation: number, start: number, size: number, gap: number, axisCoordinates: number[]): number;
|
|
63
65
|
export declare function generateUniqueId(prefix?: string): string;
|
|
64
66
|
export declare const assistiveTextStyle = "\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip: rect(0, 0, 0, 0);\n\twhite-space: nowrap;\n\tborder-width: 0;\n";
|
|
65
67
|
export declare function getElementMidpoint(element: HTMLElement): {
|
|
@@ -48,7 +48,11 @@ export class PointerService {
|
|
|
48
48
|
isPointerInside(element) {
|
|
49
49
|
const rect = element.getBoundingClientRect();
|
|
50
50
|
const { x, y } = this.#position;
|
|
51
|
-
|
|
51
|
+
// Use scrollWidth/scrollHeight to account for content that overflows the element
|
|
52
|
+
// (e.g. grid columns that extend beyond a scrollable parent with overflow: hidden).
|
|
53
|
+
const width = Math.max(rect.width, element.scrollWidth);
|
|
54
|
+
const height = Math.max(rect.height, element.scrollHeight);
|
|
55
|
+
return x >= rect.left && x <= rect.left + width && y >= rect.top && y <= rect.top + height;
|
|
52
56
|
}
|
|
53
57
|
get keyboardControlsActive() {
|
|
54
58
|
return this.#keyboardController.active;
|
|
@@ -557,9 +561,9 @@ export class GridDimensionTracker {
|
|
|
557
561
|
const style = window.getComputedStyle(parent);
|
|
558
562
|
const overflowY = style.getPropertyValue('overflow-y');
|
|
559
563
|
const overflowX = style.getPropertyValue('overflow-x');
|
|
560
|
-
const isScrollable = ((overflowY === 'auto' || overflowY === 'scroll') &&
|
|
564
|
+
const isScrollable = ((overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'hidden') &&
|
|
561
565
|
parent.scrollHeight > parent.clientHeight) ||
|
|
562
|
-
((overflowX === 'auto' || overflowX === 'scroll') &&
|
|
566
|
+
((overflowX === 'auto' || overflowX === 'scroll' || overflowX === 'hidden') &&
|
|
563
567
|
parent.scrollWidth > parent.clientWidth);
|
|
564
568
|
if (isScrollable) {
|
|
565
569
|
ancestors.push(parent);
|
|
@@ -574,32 +578,45 @@ export class GridDimensionTracker {
|
|
|
574
578
|
}
|
|
575
579
|
this.#pointerPosition.x = clientX;
|
|
576
580
|
this.#pointerPosition.y = clientY;
|
|
577
|
-
|
|
578
|
-
|
|
581
|
+
// DEBUG: trace scroll offset issue
|
|
582
|
+
const gridRef = this.#grid.ref;
|
|
583
|
+
const freshRect = gridRef.getBoundingClientRect();
|
|
584
|
+
// Walk up to find the scrollable parent (board element)
|
|
585
|
+
let scrollParent = gridRef.parentElement;
|
|
586
|
+
while (scrollParent && scrollParent.scrollLeft === 0 && scrollParent !== document.documentElement) {
|
|
587
|
+
scrollParent = scrollParent.parentElement;
|
|
588
|
+
}
|
|
589
|
+
let xCell = findCell(clientX, this.#dimensions.left, contentSize(this.#dimensions.columns, this.#dimensions.columnGap), this.#dimensions.columnGap, this.#dimensions.columns);
|
|
590
|
+
let yCell = findCell(clientY, this.#dimensions.top, contentSize(this.#dimensions.rows, this.#dimensions.rowGap), this.#dimensions.rowGap, this.#dimensions.rows);
|
|
579
591
|
return {
|
|
580
592
|
row: yCell,
|
|
581
593
|
column: xCell
|
|
582
594
|
};
|
|
583
595
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const proportionAlong = (pointerLocation - base) / (subtotal - base);
|
|
597
|
-
if (pointerLocation < subtotal) {
|
|
598
|
-
return i + proportionAlong;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
596
|
+
}
|
|
597
|
+
export function contentSize(axisCoordinates, gap) {
|
|
598
|
+
const totalCells = axisCoordinates.reduce((sum, size) => sum + size, 0);
|
|
599
|
+
const totalGaps = Math.max(0, axisCoordinates.length - 1) * gap;
|
|
600
|
+
return totalCells + totalGaps;
|
|
601
|
+
}
|
|
602
|
+
export function findCell(pointerLocation, start, size, gap, axisCoordinates) {
|
|
603
|
+
// If outside the axis, then return the ends.
|
|
604
|
+
if (pointerLocation < start) {
|
|
605
|
+
return 0;
|
|
606
|
+
}
|
|
607
|
+
if (pointerLocation >= start + size) {
|
|
601
608
|
return axisCoordinates.length;
|
|
602
609
|
}
|
|
610
|
+
let subtotal = start - gap / 2;
|
|
611
|
+
for (let i = 0; i < axisCoordinates.length; i++) {
|
|
612
|
+
const base = subtotal;
|
|
613
|
+
subtotal += axisCoordinates[i] + gap;
|
|
614
|
+
const proportionAlong = (pointerLocation - base) / (subtotal - base);
|
|
615
|
+
if (pointerLocation < subtotal) {
|
|
616
|
+
return i + proportionAlong;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return axisCoordinates.length;
|
|
603
620
|
}
|
|
604
621
|
let uniqueIdIndex = 0;
|
|
605
622
|
export function generateUniqueId(prefix = 'flexi-') {
|
|
@@ -7,11 +7,13 @@ import type { InternalFlexiBoardController } from '../board/controller.svelte.js
|
|
|
7
7
|
import type { FlexiTargetController } from './base.svelte.js';
|
|
8
8
|
import { SvelteSet } from 'svelte/reactivity';
|
|
9
9
|
import type { FlexiTargetActionWidget, FlexiTargetConfiguration, FlexiTargetPartialConfiguration } from './types.js';
|
|
10
|
+
import type { FlexiRegistryEntry, FlexiWidgetLayoutEntry } from '../board/types.js';
|
|
10
11
|
export declare class InternalFlexiTargetController implements FlexiTargetController {
|
|
11
12
|
#private;
|
|
12
13
|
provider: InternalFlexiBoardController;
|
|
13
14
|
providerWidgetDefaults?: FlexiWidgetDefaults;
|
|
14
15
|
key: string;
|
|
16
|
+
registry?: Record<string, FlexiRegistryEntry>;
|
|
15
17
|
config: FlexiTargetConfiguration;
|
|
16
18
|
constructor(provider: InternalFlexiBoardController, key: string, config?: FlexiTargetPartialConfiguration);
|
|
17
19
|
createGrid(): FlexiGrid;
|
|
@@ -25,18 +27,20 @@ export declare class InternalFlexiTargetController implements FlexiTargetControl
|
|
|
25
27
|
deleteWidget(widget: FlexiWidgetController): boolean;
|
|
26
28
|
/**
|
|
27
29
|
* Imports a layout of widgets into this target, replacing any existing widgets.
|
|
30
|
+
* Widgets with types not found in the registry will be skipped with a warning.
|
|
28
31
|
* @param layout The layout to import.
|
|
29
32
|
*/
|
|
30
|
-
importLayout(layout:
|
|
33
|
+
importLayout(layout: FlexiWidgetLayoutEntry[]): void;
|
|
31
34
|
/**
|
|
32
35
|
* Exports the current layout of widgets from this target.
|
|
33
36
|
* @returns The layout of widgets.
|
|
34
37
|
*/
|
|
35
|
-
exportLayout():
|
|
38
|
+
exportLayout(): FlexiWidgetLayoutEntry[];
|
|
36
39
|
onPointerEnterTarget(event: TargetEvent): void;
|
|
37
40
|
onPointerLeaveTarget(event: TargetEvent): void;
|
|
38
41
|
restorePreGrabSnapshot(): void;
|
|
39
42
|
forgetPreGrabSnapshot(): void;
|
|
43
|
+
hasPreGrabSnapshot(): boolean;
|
|
40
44
|
applyGridPostCompletionOperations(): void;
|
|
41
45
|
cancelDrop(): void;
|
|
42
46
|
tryDropWidget(widget: InternalFlexiWidgetController): boolean;
|
|
@@ -76,6 +80,7 @@ export declare class InternalFlexiTargetController implements FlexiTargetControl
|
|
|
76
80
|
get grid(): FlexiGrid;
|
|
77
81
|
get dropzoneWidget(): InternalFlexiWidgetController | null;
|
|
78
82
|
set dropzoneWidget(value: InternalFlexiWidgetController | null);
|
|
83
|
+
get shouldRenderDropzoneWidget(): boolean;
|
|
79
84
|
get widgets(): SvelteSet<FlexiWidgetController>;
|
|
80
85
|
get internalWidgets(): SvelteSet<InternalFlexiWidgetController>;
|
|
81
86
|
get orderedWidgets(): InternalFlexiWidgetController[];
|