@hkdigital/lib-sveltekit 0.2.4 → 0.2.6
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/classes/logging/Logger.js +1 -1
- package/dist/components/drag-drop/Draggable.svelte +20 -10
- package/dist/components/layout/grid-layers/GridLayers.svelte +16 -140
- package/dist/components/layout/grid-layers/GridLayers.svelte.d.ts +2 -22
- package/dist/components/layout/grid-layers/GridLayers.svelte__heightFrom__ +372 -0
- package/dist/features/presenter/ImageSlide.svelte +2 -2
- package/dist/features/presenter/ImageSlide.svelte.d.ts +2 -2
- package/dist/features/presenter/Presenter.svelte +4 -2
- package/dist/themes/hkdev/components/drag-drop/draggable.css +15 -6
- package/package.json +1 -1
- package/dist/components/layout/grid-layers/GridLayers.svelte__ +0 -167
@@ -34,7 +34,7 @@
|
|
34
34
|
|
35
35
|
import { EventEmitter } from '../events';
|
36
36
|
|
37
|
-
import { DEBUG, INFO, WARN, ERROR, FATAL,
|
37
|
+
import { DEBUG, INFO, WARN, ERROR, FATAL, LEVELS } from './constants.js';
|
38
38
|
|
39
39
|
/**
|
40
40
|
* Logger class for consistent logging across services
|
@@ -8,8 +8,7 @@
|
|
8
8
|
IDLE,
|
9
9
|
DRAGGING,
|
10
10
|
DRAG_PREVIEW,
|
11
|
-
DROPPING
|
12
|
-
DRAG_DISABLED
|
11
|
+
DROPPING
|
13
12
|
} from '../../constants/state-labels/drag-states.js';
|
14
13
|
|
15
14
|
|
@@ -99,14 +98,24 @@
|
|
99
98
|
let customPreviewSet = $state(false);
|
100
99
|
let elementRect = $state(null);
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
101
|
+
// Track if current draggable can drop in the active zone
|
102
|
+
let canDropInActiveZone = $derived.by(() => {
|
103
|
+
if (currentState !== DRAGGING || !dragState.activeDropZone) return false;
|
104
|
+
|
105
|
+
const activeZone = dragState.dropZones.get(dragState.activeDropZone);
|
106
|
+
return activeZone?.canDrop || false;
|
107
|
+
});
|
108
|
+
|
109
|
+
// Computed state object for CSS classes
|
110
|
+
let stateObject = $derived({
|
111
|
+
idle: currentState === IDLE,
|
112
|
+
dragging: currentState === DRAGGING,
|
113
|
+
'drag-preview': currentState === DRAG_PREVIEW,
|
114
|
+
dropping: currentState === DROPPING,
|
115
|
+
'drag-disabled': disabled || !canDrag(item),
|
116
|
+
'can-drop': currentState === DRAGGING && canDropInActiveZone,
|
117
|
+
'cannot-drop': currentState === DRAGGING && dragState.activeDropZone && !canDropInActiveZone
|
118
|
+
});
|
110
119
|
|
111
120
|
let stateClasses = $derived(toStateClasses(stateObject));
|
112
121
|
|
@@ -465,6 +474,7 @@ function handleTouchMove(event) {
|
|
465
474
|
{#if draggingSnippet && showPreview && elementRect}
|
466
475
|
<div
|
467
476
|
data-companion="drag-preview-follower"
|
477
|
+
class={stateClasses}
|
468
478
|
style="position: fixed; z-index: 9999; pointer-events: none;"
|
469
479
|
style:left="{previewX}px"
|
470
480
|
style:top="{previewY}px"
|
@@ -1,187 +1,63 @@
|
|
1
1
|
<script>
|
2
|
-
import { onMount, onDestroy } from 'svelte';
|
3
|
-
import { setupLayerObserver, measureTargetLayer } from './util.js';
|
4
|
-
|
5
2
|
/**
|
3
|
+
* GridLayers Component
|
4
|
+
*
|
5
|
+
* Creates a single-cell grid where all children occupy the same space,
|
6
|
+
* enabling layered layouts with natural height behavior.
|
7
|
+
*
|
6
8
|
* @type {{
|
7
9
|
* base?: string,
|
8
10
|
* bg?: string,
|
9
11
|
* padding?: string,
|
10
12
|
* margin?: string,
|
11
|
-
* height?: string,
|
12
13
|
* classes?: string,
|
13
14
|
* style?: string,
|
14
|
-
*
|
15
|
-
* cellBg?: string,
|
16
|
-
* cellPadding?: string,
|
17
|
-
* cellMargin?: string,
|
18
|
-
* cellClasses?: string,
|
19
|
-
* cellStyle?: string,
|
20
|
-
* heightFrom?: string|null,
|
15
|
+
* overflow?: string,
|
21
16
|
* children: import('svelte').Snippet,
|
22
|
-
* cellAttrs?: { [attr: string]: any },
|
23
17
|
* [attr: string]: any
|
24
18
|
* }}
|
25
19
|
*/
|
26
20
|
const {
|
27
|
-
//
|
21
|
+
// Container styles
|
28
22
|
base = '',
|
29
23
|
bg = '',
|
30
24
|
padding = '',
|
31
25
|
margin = '',
|
32
|
-
height = 'h-full',
|
33
26
|
classes = '',
|
34
27
|
style = '',
|
35
|
-
|
36
|
-
cellBg = '',
|
37
|
-
cellPadding = '',
|
38
|
-
cellMargin = '',
|
39
|
-
cellClasses = '',
|
40
|
-
cellStyle = '',
|
41
|
-
|
42
|
-
// Behavior
|
43
|
-
heightFrom = null,
|
28
|
+
overflow = '',
|
44
29
|
|
45
|
-
//
|
46
|
-
cellAttrs = {},
|
30
|
+
// Content
|
47
31
|
children,
|
48
32
|
|
49
33
|
// Attributes
|
50
34
|
...attrs
|
51
35
|
} = $props();
|
52
36
|
|
53
|
-
//
|
54
|
-
let gridContainer = $state(null);
|
55
|
-
let gridContent = $state(null);
|
56
|
-
let calculatedHeight = $state(0);
|
57
|
-
let observer = $state(null);
|
58
|
-
let targetLayer = $state(null);
|
59
|
-
let isFirstRender = $state(heightFrom !== null); // Start with true if heightFrom is provided
|
60
|
-
let preCalculatedHeight = $state(0);
|
61
|
-
|
62
|
-
// Derived container style that updates reactively when dependencies change
|
37
|
+
// Build the inline style
|
63
38
|
let containerStyle = $derived.by(() => {
|
64
|
-
const styles = [];
|
39
|
+
const styles = ['grid-template: 1fr / 1fr;'];
|
65
40
|
|
66
41
|
if (style) {
|
67
42
|
styles.push(style);
|
68
43
|
}
|
69
44
|
|
70
|
-
if (heightFrom && calculatedHeight > 0) {
|
71
|
-
styles.push(`height: ${calculatedHeight}px;`);
|
72
|
-
}
|
73
|
-
|
74
45
|
return styles.join(' ');
|
75
46
|
});
|
76
|
-
|
77
|
-
/**
|
78
|
-
* Handler for height changes detected by the observer
|
79
|
-
* @param {number} newHeight - The new calculated height
|
80
|
-
*/
|
81
|
-
function handleHeightChange(newHeight) {
|
82
|
-
calculatedHeight = newHeight;
|
83
|
-
}
|
84
|
-
|
85
|
-
/**
|
86
|
-
* Initialize height measurement and observation
|
87
|
-
*/
|
88
|
-
function initializeHeightTracking() {
|
89
|
-
if (!heightFrom || !gridContent) return;
|
90
|
-
|
91
|
-
// Measure the layer initially
|
92
|
-
const { element, height } = measureTargetLayer(gridContent, heightFrom);
|
93
|
-
|
94
|
-
if (element) {
|
95
|
-
targetLayer = element;
|
96
|
-
calculatedHeight = height;
|
97
|
-
|
98
|
-
// Setup observer for future changes
|
99
|
-
observer = setupLayerObserver(element, handleHeightChange);
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
// Initialize on mount with the two-pass rendering approach
|
104
|
-
onMount(() => {
|
105
|
-
if (heightFrom) {
|
106
|
-
// First render: measure invisibly
|
107
|
-
requestAnimationFrame(() => {
|
108
|
-
if (gridContent) {
|
109
|
-
const { element, height } = measureTargetLayer(gridContent, heightFrom);
|
110
|
-
|
111
|
-
if (element) {
|
112
|
-
targetLayer = element;
|
113
|
-
preCalculatedHeight = height;
|
114
|
-
|
115
|
-
// Second render: show with correct height
|
116
|
-
requestAnimationFrame(() => {
|
117
|
-
calculatedHeight = preCalculatedHeight;
|
118
|
-
isFirstRender = false;
|
119
|
-
|
120
|
-
// Setup observer for future changes
|
121
|
-
observer = setupLayerObserver(element, handleHeightChange);
|
122
|
-
});
|
123
|
-
} else {
|
124
|
-
// No target layer found, just show the component
|
125
|
-
isFirstRender = false;
|
126
|
-
}
|
127
|
-
} else {
|
128
|
-
// No grid content, just show the component
|
129
|
-
isFirstRender = false;
|
130
|
-
}
|
131
|
-
});
|
132
|
-
} else {
|
133
|
-
// No heightFrom, no need for measurement
|
134
|
-
isFirstRender = false;
|
135
|
-
}
|
136
|
-
});
|
137
|
-
|
138
|
-
// Effect to re-setup observer when either the target layer or heightFrom changes
|
139
|
-
$effect(() => {
|
140
|
-
// Only handle changes after initial setup
|
141
|
-
if (!isFirstRender && heightFrom && gridContent && !observer) {
|
142
|
-
initializeHeightTracking();
|
143
|
-
}
|
144
|
-
});
|
145
|
-
|
146
|
-
// Clean up on destroy
|
147
|
-
onDestroy(() => {
|
148
|
-
if (observer) {
|
149
|
-
observer.disconnect();
|
150
|
-
observer = null;
|
151
|
-
}
|
152
|
-
});
|
153
|
-
|
154
47
|
</script>
|
155
48
|
|
156
49
|
<div
|
157
50
|
data-component="grid-layers"
|
158
|
-
|
159
|
-
class="relative {isFirstRender ? 'invisible' : ''} {base} {bg} {heightFrom ? '' : height} {classes} {margin} {padding}"
|
51
|
+
class="grid {base} {bg} {classes} {margin} {padding} {overflow}"
|
160
52
|
style={containerStyle}
|
161
53
|
{...attrs}
|
162
54
|
>
|
163
|
-
|
164
|
-
data-section="grid"
|
165
|
-
bind:this={gridContent}
|
166
|
-
class="absolute inset-0 grid {cellBase} {cellBg} {cellPadding} {cellMargin} {cellClasses}"
|
167
|
-
style={cellStyle}
|
168
|
-
{...cellAttrs}
|
169
|
-
>
|
170
|
-
{@render children()}
|
171
|
-
</div>
|
55
|
+
{@render children()}
|
172
56
|
</div>
|
173
57
|
|
174
58
|
<style>
|
175
|
-
/* All children
|
176
|
-
|
177
|
-
|
178
|
-
grid-template-columns: 1fr;
|
179
|
-
grid-template-rows: 1fr;
|
180
|
-
}
|
181
|
-
|
182
|
-
[data-section='grid'] > :global(*) {
|
183
|
-
grid-column: 1;
|
184
|
-
grid-row: 1;
|
185
|
-
z-index: 0; /* Base z-index to allow explicit stacking order */
|
59
|
+
/* All direct children occupy the same grid area */
|
60
|
+
div > :global(*) {
|
61
|
+
grid-area: 1 / 1;
|
186
62
|
}
|
187
63
|
</style>
|
@@ -7,20 +7,10 @@ type GridLayers = {
|
|
7
7
|
bg?: string;
|
8
8
|
padding?: string;
|
9
9
|
margin?: string;
|
10
|
-
height?: string;
|
11
10
|
classes?: string;
|
12
11
|
style?: string;
|
13
|
-
|
14
|
-
cellBg?: string;
|
15
|
-
cellPadding?: string;
|
16
|
-
cellMargin?: string;
|
17
|
-
cellClasses?: string;
|
18
|
-
cellStyle?: string;
|
19
|
-
heightFrom?: string;
|
12
|
+
overflow?: string;
|
20
13
|
children: Snippet<[]>;
|
21
|
-
cellAttrs?: {
|
22
|
-
[attr: string]: any;
|
23
|
-
};
|
24
14
|
}>): void;
|
25
15
|
};
|
26
16
|
declare const GridLayers: import("svelte").Component<{
|
@@ -29,18 +19,8 @@ declare const GridLayers: import("svelte").Component<{
|
|
29
19
|
bg?: string;
|
30
20
|
padding?: string;
|
31
21
|
margin?: string;
|
32
|
-
height?: string;
|
33
22
|
classes?: string;
|
34
23
|
style?: string;
|
35
|
-
|
36
|
-
cellBg?: string;
|
37
|
-
cellPadding?: string;
|
38
|
-
cellMargin?: string;
|
39
|
-
cellClasses?: string;
|
40
|
-
cellStyle?: string;
|
41
|
-
heightFrom?: string | null;
|
24
|
+
overflow?: string;
|
42
25
|
children: import("svelte").Snippet;
|
43
|
-
cellAttrs?: {
|
44
|
-
[attr: string]: any;
|
45
|
-
};
|
46
26
|
}, {}, "">;
|
@@ -0,0 +1,372 @@
|
|
1
|
+
<script>
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
3
|
+
import { setupLayerObserver, measureTargetLayer } from './util.js';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* # GridLayers Component
|
7
|
+
*
|
8
|
+
* A Svelte 5 component that creates a layered grid layout where all
|
9
|
+
* children occupy the same grid area, allowing for overlapping content with
|
10
|
+
* precise positioning control.
|
11
|
+
*
|
12
|
+
* ## Overview
|
13
|
+
*
|
14
|
+
* GridLayers uses CSS Grid to stack multiple elements in the same grid cell
|
15
|
+
* (1x1 grid), enabling layered layouts without absolute positioning on the
|
16
|
+
* children. This approach maintains the natural flow and sizing behavior of
|
17
|
+
* grid items while allowing them to overlap.
|
18
|
+
*
|
19
|
+
* ## Height Control
|
20
|
+
*
|
21
|
+
* The component offers two methods for controlling height:
|
22
|
+
*
|
23
|
+
* ### 1. Fixed Height (default)
|
24
|
+
*
|
25
|
+
* Use the `height` prop with Tailwind classes:
|
26
|
+
* ```svelte
|
27
|
+
* <GridLayers height="h-[500px]">
|
28
|
+
* <!-- content -->
|
29
|
+
* </GridLayers>
|
30
|
+
* ```
|
31
|
+
*
|
32
|
+
* ### 2. Dynamic Height
|
33
|
+
* Use the `heightFrom` prop to make the container's height match a specific
|
34
|
+
* child layer:
|
35
|
+
*
|
36
|
+
* ```svelte
|
37
|
+
* <GridLayers heightFrom="content">
|
38
|
+
* <div data-layer="content">
|
39
|
+
* <!-- This layer's height determines the container height -->
|
40
|
+
* </div>
|
41
|
+
* <div data-layer="overlay">
|
42
|
+
* <!-- Other layers adapt to the container -->
|
43
|
+
* </div>
|
44
|
+
* </GridLayers>
|
45
|
+
* ```
|
46
|
+
*
|
47
|
+
* The `heightFrom` value should match a child's `data-layer` attribute.
|
48
|
+
* The component will:
|
49
|
+
* - Initially render invisibly to measure the target layer
|
50
|
+
* - Apply the measured height to the container
|
51
|
+
* - Watch for changes and update automatically
|
52
|
+
*
|
53
|
+
* ## Positioning Layers
|
54
|
+
*
|
55
|
+
* Each child element can be positioned within the grid cell using Tailwind's
|
56
|
+
* alignment utilities:
|
57
|
+
*
|
58
|
+
* ### justify-self (Horizontal Alignment)
|
59
|
+
* - `justify-self-start` - Align to the left
|
60
|
+
* - `justify-self-center` - Center horizontally
|
61
|
+
* - `justify-self-end` - Align to the right
|
62
|
+
* - `justify-self-stretch` - Stretch to full width (default)
|
63
|
+
*
|
64
|
+
* ### self (Vertical Alignment)
|
65
|
+
* - `self-start` - Align to the top
|
66
|
+
* - `self-center` - Center vertically
|
67
|
+
* - `self-end` - Align to the bottom
|
68
|
+
* - `self-stretch` - Stretch to full height (default)
|
69
|
+
*
|
70
|
+
* ### Combining Positions
|
71
|
+
* ```svelte
|
72
|
+
* <GridLayers height="h-[400px]">
|
73
|
+
* <div class="justify-self-start self-start">Top Left</div>
|
74
|
+
* <div class="justify-self-center self-center">Centered</div>
|
75
|
+
* <div class="justify-self-end self-end">Bottom Right</div>
|
76
|
+
* </GridLayers>
|
77
|
+
* ```
|
78
|
+
*
|
79
|
+
* ### Fine-tuning with Margins
|
80
|
+
* For precise positioning adjustments, use margins:
|
81
|
+
* ```svelte
|
82
|
+
* <div class="justify-self-end self-end mr-4 mb-4">
|
83
|
+
* <!-- Positioned at bottom-right with 1rem spacing -->
|
84
|
+
* </div>
|
85
|
+
* ```
|
86
|
+
*
|
87
|
+
* ## Technical Implementation
|
88
|
+
*
|
89
|
+
* ### The Grid Container
|
90
|
+
* The inner grid container uses `absolute inset-0` which:
|
91
|
+
* - Positions it absolutely within the relative parent
|
92
|
+
* - `inset-0` is shorthand for `top: 0, right: 0, bottom: 0, left: 0`
|
93
|
+
* - Makes the grid fill the entire parent container
|
94
|
+
* - Ensures the grid respects the parent's dimensions (fixed or dynamic)
|
95
|
+
*
|
96
|
+
* This approach creates a stable positioning context while maintaining the
|
97
|
+
* parent's flow in the document.
|
98
|
+
*
|
99
|
+
* ### Grid Structure
|
100
|
+
* All children are assigned to the same grid cell:
|
101
|
+
* ```css
|
102
|
+
* grid-template-columns: 1fr;
|
103
|
+
* grid-template-rows: 1fr;
|
104
|
+
* grid-column: 1;
|
105
|
+
* grid-row: 1;
|
106
|
+
* ```
|
107
|
+
*
|
108
|
+
* ## Overflow Behavior
|
109
|
+
*
|
110
|
+
* When a layer's content exceeds the container bounds:
|
111
|
+
*
|
112
|
+
* ### Default Behavior
|
113
|
+
* - Content will overflow and be visible outside the container
|
114
|
+
* - This can break layouts or create unwanted scrollbars
|
115
|
+
*
|
116
|
+
* ### Controlling Overflow
|
117
|
+
* Add overflow utilities to the container:
|
118
|
+
* ```svelte
|
119
|
+
* <!-- Hide overflow -->
|
120
|
+
* <GridLayers classes="overflow-hidden">
|
121
|
+
*
|
122
|
+
* <!-- Scroll if needed -->
|
123
|
+
* <GridLayers classes="overflow-auto">
|
124
|
+
*
|
125
|
+
* <!-- Scroll specific layer -->
|
126
|
+
* <GridLayers>
|
127
|
+
* <div class="overflow-auto">
|
128
|
+
* <!-- Scrollable content -->
|
129
|
+
* </div>
|
130
|
+
* </GridLayers>
|
131
|
+
* ```
|
132
|
+
*
|
133
|
+
* ### Best Practices for Overflow
|
134
|
+
* 1. Use `overflow-hidden` on the container when layers should be clipped
|
135
|
+
* 2. Apply `overflow-auto` to specific layers that need scrolling
|
136
|
+
* 3. Consider using `max-h-*` classes on content layers
|
137
|
+
* 4. Test with different content sizes to ensure proper behavior
|
138
|
+
*
|
139
|
+
* ## Z-Index Stacking
|
140
|
+
*
|
141
|
+
* Layers have a base `z-index: 0` and stack in DOM order. Control stacking with:
|
142
|
+
* ```svelte
|
143
|
+
* <div class="z-10">Top layer</div>
|
144
|
+
* <div class="z-0">Base layer</div>
|
145
|
+
* <div class="z-20">Topmost layer</div>
|
146
|
+
* ```
|
147
|
+
*
|
148
|
+
* ## Common Patterns
|
149
|
+
*
|
150
|
+
* ### Header/Content/Footer
|
151
|
+
* ```svelte
|
152
|
+
* <GridLayers heightFrom="content">
|
153
|
+
* <div data-layer="header" class="self-start">
|
154
|
+
* <header>Fixed header</header>
|
155
|
+
* </div>
|
156
|
+
* <div data-layer="content" class="self-center">
|
157
|
+
* <main>Dynamic content</main>
|
158
|
+
* </div>
|
159
|
+
* <div data-layer="footer" class="self-end">
|
160
|
+
* <footer>Fixed footer</footer>
|
161
|
+
* </div>
|
162
|
+
* </GridLayers>
|
163
|
+
* ```
|
164
|
+
*
|
165
|
+
* ### Centered Overlay
|
166
|
+
* ```svelte
|
167
|
+
* <GridLayers height="h-screen">
|
168
|
+
* <div class="z-0">
|
169
|
+
* <img src="background.jpg" class="w-full h-full object-cover" />
|
170
|
+
* </div>
|
171
|
+
* <div class="z-10 justify-self-center self-center">
|
172
|
+
* <div class="bg-white p-8 rounded shadow-lg">
|
173
|
+
* Centered content over background
|
174
|
+
* </div>
|
175
|
+
* </div>
|
176
|
+
* </GridLayers>
|
177
|
+
* ```
|
178
|
+
*
|
179
|
+
* ### Corner Badges
|
180
|
+
* ```svelte
|
181
|
+
* <GridLayers height="h-64">
|
182
|
+
* <div class="justify-self-end self-start m-4 z-10">
|
183
|
+
* <span class="badge">New</span>
|
184
|
+
* </div>
|
185
|
+
* <div>
|
186
|
+
* Main content
|
187
|
+
* </div>
|
188
|
+
* </GridLayers>
|
189
|
+
* ```
|
190
|
+
*/
|
191
|
+
|
192
|
+
/**
|
193
|
+
* @type {{
|
194
|
+
* base?: string,
|
195
|
+
* bg?: string,
|
196
|
+
* padding?: string,
|
197
|
+
* margin?: string,
|
198
|
+
* height?: string,
|
199
|
+
* classes?: string,
|
200
|
+
* style?: string,
|
201
|
+
* cellBase?: string,
|
202
|
+
* cellBg?: string,
|
203
|
+
* cellPadding?: string,
|
204
|
+
* cellMargin?: string,
|
205
|
+
* cellClasses?: string,
|
206
|
+
* cellStyle?: string,
|
207
|
+
* heightFrom?: string|null,
|
208
|
+
* children: import('svelte').Snippet,
|
209
|
+
* cellAttrs?: { [attr: string]: any },
|
210
|
+
* [attr: string]: any
|
211
|
+
* }}
|
212
|
+
*/
|
213
|
+
const {
|
214
|
+
// Style
|
215
|
+
base = '',
|
216
|
+
bg = '',
|
217
|
+
padding = '',
|
218
|
+
margin = '',
|
219
|
+
height = 'h-full',
|
220
|
+
classes = '',
|
221
|
+
style = '',
|
222
|
+
cellBase = '',
|
223
|
+
cellBg = '',
|
224
|
+
cellPadding = '',
|
225
|
+
cellMargin = '',
|
226
|
+
cellClasses = '',
|
227
|
+
cellStyle = '',
|
228
|
+
|
229
|
+
// Behavior
|
230
|
+
heightFrom = null,
|
231
|
+
|
232
|
+
// Props
|
233
|
+
cellAttrs = {},
|
234
|
+
children,
|
235
|
+
|
236
|
+
// Attributes
|
237
|
+
...attrs
|
238
|
+
} = $props();
|
239
|
+
|
240
|
+
// Component state
|
241
|
+
let gridContent = $state(null);
|
242
|
+
let calculatedHeight = $state(0);
|
243
|
+
let observer = $state(null);
|
244
|
+
|
245
|
+
// Start with true if heightFrom is provided
|
246
|
+
let isFirstRender = $state(heightFrom !== null);
|
247
|
+
|
248
|
+
let preCalculatedHeight = $state(0);
|
249
|
+
|
250
|
+
// Derived container style that updates reactively when dependencies change
|
251
|
+
let containerStyle = $derived.by(() => {
|
252
|
+
const styles = [];
|
253
|
+
|
254
|
+
if (style) {
|
255
|
+
styles.push(style);
|
256
|
+
}
|
257
|
+
|
258
|
+
if (heightFrom && calculatedHeight > 0) {
|
259
|
+
styles.push(`height: ${calculatedHeight}px;`);
|
260
|
+
}
|
261
|
+
|
262
|
+
return styles.join(' ');
|
263
|
+
});
|
264
|
+
|
265
|
+
/**
|
266
|
+
* Handler for height changes detected by the observer
|
267
|
+
* @param {number} newHeight - The new calculated height
|
268
|
+
*/
|
269
|
+
function handleHeightChange(newHeight) {
|
270
|
+
calculatedHeight = newHeight;
|
271
|
+
}
|
272
|
+
|
273
|
+
/**
|
274
|
+
* Initialize height measurement and observation
|
275
|
+
*/
|
276
|
+
function initializeHeightTracking() {
|
277
|
+
if (!heightFrom || !gridContent) return;
|
278
|
+
|
279
|
+
// Measure the layer initially
|
280
|
+
const { element, height } = measureTargetLayer(gridContent, heightFrom);
|
281
|
+
|
282
|
+
if (element) {
|
283
|
+
calculatedHeight = height;
|
284
|
+
|
285
|
+
// Setup observer for future changes
|
286
|
+
observer = setupLayerObserver(element, handleHeightChange);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
// Initialize on mount with the two-pass rendering approach
|
291
|
+
onMount(() => {
|
292
|
+
if (heightFrom) {
|
293
|
+
// First render: measure invisibly
|
294
|
+
requestAnimationFrame(() => {
|
295
|
+
if (gridContent) {
|
296
|
+
const { element, height } = measureTargetLayer(gridContent, heightFrom);
|
297
|
+
|
298
|
+
if (element) {
|
299
|
+
preCalculatedHeight = height;
|
300
|
+
|
301
|
+
// Second render: show with correct height
|
302
|
+
requestAnimationFrame(() => {
|
303
|
+
calculatedHeight = preCalculatedHeight;
|
304
|
+
isFirstRender = false;
|
305
|
+
|
306
|
+
// Setup observer for future changes
|
307
|
+
observer = setupLayerObserver(element, handleHeightChange);
|
308
|
+
});
|
309
|
+
} else {
|
310
|
+
// No target layer found, just show the component
|
311
|
+
isFirstRender = false;
|
312
|
+
}
|
313
|
+
} else {
|
314
|
+
// No grid content, just show the component
|
315
|
+
isFirstRender = false;
|
316
|
+
}
|
317
|
+
});
|
318
|
+
} else {
|
319
|
+
// No heightFrom, no need for measurement
|
320
|
+
isFirstRender = false;
|
321
|
+
}
|
322
|
+
});
|
323
|
+
|
324
|
+
// Effect to re-setup observer when either the target layer or heightFrom changes
|
325
|
+
$effect(() => {
|
326
|
+
// Only handle changes after initial setup
|
327
|
+
if (!isFirstRender && heightFrom && gridContent && !observer) {
|
328
|
+
initializeHeightTracking();
|
329
|
+
}
|
330
|
+
});
|
331
|
+
|
332
|
+
// Clean up on destroy
|
333
|
+
onDestroy(() => {
|
334
|
+
if (observer) {
|
335
|
+
observer.disconnect();
|
336
|
+
observer = null;
|
337
|
+
}
|
338
|
+
});
|
339
|
+
|
340
|
+
</script>
|
341
|
+
|
342
|
+
<div
|
343
|
+
data-component="grid-layers"
|
344
|
+
class="relative {isFirstRender ? 'invisible' : ''} {base} {bg} {heightFrom ? '' : height} {classes} {margin} {padding}"
|
345
|
+
style={containerStyle}
|
346
|
+
{...attrs}
|
347
|
+
>
|
348
|
+
<div
|
349
|
+
data-section="grid"
|
350
|
+
bind:this={gridContent}
|
351
|
+
class="absolute inset-0 grid {cellBase} {cellBg} {cellPadding} {cellMargin} {cellClasses}"
|
352
|
+
style={cellStyle}
|
353
|
+
{...cellAttrs}
|
354
|
+
>
|
355
|
+
{@render children()}
|
356
|
+
</div>
|
357
|
+
</div>
|
358
|
+
|
359
|
+
<style>
|
360
|
+
/* All children of the layer share the same grid area
|
361
|
+
but aren't absolutely positioned */
|
362
|
+
[data-section='grid'] {
|
363
|
+
grid-template-columns: 1fr;
|
364
|
+
grid-template-rows: 1fr;
|
365
|
+
}
|
366
|
+
|
367
|
+
[data-section='grid'] > :global(*) {
|
368
|
+
grid-column: 1;
|
369
|
+
grid-row: 1;
|
370
|
+
z-index: 0; /* Base z-index to allow explicit stacking order */
|
371
|
+
}
|
372
|
+
</style>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
/**
|
5
5
|
* @type {{
|
6
|
-
* imageMeta?: import('
|
6
|
+
* imageMeta?: import('../../typedef').ImageSource,
|
7
7
|
* slideDuration?: number,
|
8
8
|
* nextSlideLabel?: string,
|
9
9
|
* presenter?: { gotoSlide: (name: string) => void, getCurrentSlideName: () => string },
|
@@ -53,7 +53,7 @@
|
|
53
53
|
});
|
54
54
|
</script>
|
55
55
|
|
56
|
-
<div class="
|
56
|
+
<div class="justify-self-stretch self-stretch grid" class:invisible={!show}>
|
57
57
|
<ImageBox
|
58
58
|
{imageMeta}
|
59
59
|
{fit}
|
@@ -3,7 +3,7 @@ type ImageSlide = {
|
|
3
3
|
$on?(type: string, callback: (e: any) => void): () => void;
|
4
4
|
$set?(props: Partial<{
|
5
5
|
[attr: string]: any;
|
6
|
-
imageMeta?:
|
6
|
+
imageMeta?: ImageSource;
|
7
7
|
slideDuration?: number;
|
8
8
|
nextSlideLabel?: string;
|
9
9
|
presenter?: {
|
@@ -20,7 +20,7 @@ type ImageSlide = {
|
|
20
20
|
};
|
21
21
|
declare const ImageSlide: import("svelte").Component<{
|
22
22
|
[attr: string]: any;
|
23
|
-
imageMeta?:
|
23
|
+
imageMeta?: import("../../typedef").ImageSource;
|
24
24
|
slideDuration?: number;
|
25
25
|
nextSlideLabel?: string;
|
26
26
|
presenter?: {
|
@@ -109,8 +109,9 @@
|
|
109
109
|
});
|
110
110
|
</script>
|
111
111
|
|
112
|
-
<GridLayers data-
|
112
|
+
<GridLayers data-feature="presenter" {classes}>
|
113
113
|
<div
|
114
|
+
data-layer="layer1"
|
114
115
|
style:z-index={presenter.layerA.z}
|
115
116
|
style:visibility={presenter.layerA.visible ? 'visible' : 'hidden'}
|
116
117
|
inert={presenter.busy}
|
@@ -122,6 +123,7 @@
|
|
122
123
|
</div>
|
123
124
|
|
124
125
|
<div
|
126
|
+
data-layer="layer2"
|
125
127
|
style:z-index={presenter.layerB.z}
|
126
128
|
style:visibility={presenter.layerB.visible ? 'visible' : 'hidden'}
|
127
129
|
inert={presenter.busy}
|
@@ -133,7 +135,7 @@
|
|
133
135
|
</div>
|
134
136
|
|
135
137
|
{#if loadingSnippet && presenter.loadingSpinner}
|
136
|
-
<div class="
|
138
|
+
<div class="justify-self-stretch self-stretch overflow-hidden grid z-[20]">
|
137
139
|
{@render loadingSnippet()}
|
138
140
|
</div>
|
139
141
|
{/if}
|
@@ -7,10 +7,9 @@
|
|
7
7
|
}
|
8
8
|
|
9
9
|
[data-component='draggable']:not(.state-dragging):not(.state-drag-disabled) {
|
10
|
-
cursor: grab;
|
10
|
+
cursor: grab; /* Open hand when hovering draggable items */
|
11
11
|
}
|
12
12
|
|
13
|
-
|
14
13
|
/*[data-component='draggable']:active {
|
15
14
|
cursor: grabbing;
|
16
15
|
}*/
|
@@ -42,6 +41,20 @@
|
|
42
41
|
filter: grayscale(0.5);
|
43
42
|
}
|
44
43
|
|
44
|
+
/* Preview follower */
|
45
|
+
|
46
|
+
[data-companion='drag-preview-follower'] {
|
47
|
+
/*box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);*/
|
48
|
+
}
|
49
|
+
|
50
|
+
[data-companion='drag-preview-follower'].state-can-drop {
|
51
|
+
box-shadow: 0 0 12px rgba(74, 222, 128, 0.5);
|
52
|
+
}
|
53
|
+
|
54
|
+
[data-companion='drag-preview-follower'].state-cannot-drop {
|
55
|
+
box-shadow: 0 0 12px rgba(239, 68, 68, 0.5);
|
56
|
+
}
|
57
|
+
|
45
58
|
/* Animations */
|
46
59
|
@keyframes drop-finish {
|
47
60
|
0% {
|
@@ -57,8 +70,4 @@
|
|
57
70
|
opacity: 1;
|
58
71
|
}
|
59
72
|
}
|
60
|
-
|
61
|
-
& [data-companion="drag-preview-follower"] {
|
62
|
-
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
63
|
-
}
|
64
73
|
}
|
package/package.json
CHANGED
@@ -1,167 +0,0 @@
|
|
1
|
-
<script>
|
2
|
-
/**
|
3
|
-
* Grid Layers Component
|
4
|
-
*
|
5
|
-
* A component that creates a single-cell grid where all children exist
|
6
|
-
* in the same grid cell, allowing them to be positioned independently
|
7
|
-
* and stacked on top of each other. Perfect for complex layouts like
|
8
|
-
* overlaying text on images, card stacks, positioning UI elements, etc.
|
9
|
-
*
|
10
|
-
* Each child can use grid positioning properties (justify-self-*, self-*)
|
11
|
-
* for precise placement. Children can control stacking order with z-index.
|
12
|
-
*
|
13
|
-
* @example Basic usage with 9-position grid
|
14
|
-
* ```html
|
15
|
-
* <GridLayers classes="border w-[500px] h-[500px]">
|
16
|
-
* <!-- Top Row -->
|
17
|
-
* <div class="justify-self-start self-start">
|
18
|
-
* <div class="bg-blue-500 w-[100px] h-[100px]">
|
19
|
-
* Top Left
|
20
|
-
* </div>
|
21
|
-
* </div>
|
22
|
-
* <div class="justify-self-center self-start">
|
23
|
-
* <div class="bg-blue-300 w-[100px] h-[100px]">
|
24
|
-
* Top Center
|
25
|
-
* </div>
|
26
|
-
* </div>
|
27
|
-
* <div class="justify-self-end self-start">
|
28
|
-
* <div class="bg-blue-500 w-[100px] h-[100px]">
|
29
|
-
* Top Right
|
30
|
-
* </div>
|
31
|
-
* </div>
|
32
|
-
*
|
33
|
-
* <!-- Middle Row -->
|
34
|
-
* <div class="justify-self-start self-center">
|
35
|
-
* <div class="bg-green-500 w-[100px] h-[100px]">
|
36
|
-
* Middle Left
|
37
|
-
* </div>
|
38
|
-
* </div>
|
39
|
-
* <div class="justify-self-center self-center">
|
40
|
-
* <div class="bg-green-300 w-[100px] h-[100px]">
|
41
|
-
* Middle Center
|
42
|
-
* </div>
|
43
|
-
* </div>
|
44
|
-
* <div class="justify-self-end self-center">
|
45
|
-
* <div class="bg-green-500 w-[100px] h-[100px]">
|
46
|
-
* Middle Right
|
47
|
-
* </div>
|
48
|
-
* </div>
|
49
|
-
*
|
50
|
-
* <!-- Bottom Row -->
|
51
|
-
* <div class="justify-self-start self-end">
|
52
|
-
* <div class="bg-red-500 w-[100px] h-[100px]">
|
53
|
-
* Bottom Left
|
54
|
-
* </div>
|
55
|
-
* </div>
|
56
|
-
* <div class="justify-self-center self-end">
|
57
|
-
* <div class="bg-red-300 w-[100px] h-[100px]">
|
58
|
-
* Bottom Center
|
59
|
-
* </div>
|
60
|
-
* </div>
|
61
|
-
* <div class="justify-self-end self-end">
|
62
|
-
* <div class="bg-red-500 w-[100px] h-[100px]">
|
63
|
-
* Bottom Right
|
64
|
-
* </div>
|
65
|
-
* </div>
|
66
|
-
* </GridLayers>
|
67
|
-
* ```
|
68
|
-
*
|
69
|
-
* @example Text over image
|
70
|
-
* ```html
|
71
|
-
* <GridLayers classes="w-full h-[300px]">
|
72
|
-
* <!-- Background image layer -->
|
73
|
-
* <div class="justify-self-stretch self-stretch z-0">
|
74
|
-
* <img
|
75
|
-
* src="/images/landscape.jpg"
|
76
|
-
* alt="Landscape"
|
77
|
-
* class="w-full h-full object-cover"
|
78
|
-
* />
|
79
|
-
* </div>
|
80
|
-
*
|
81
|
-
* <!-- Text overlay layer -->
|
82
|
-
* <div class="justify-self-center self-center z-10">
|
83
|
-
* <div class="bg-black/50 p-16up text-white
|
84
|
-
* font-ui rounded-md">
|
85
|
-
* <h2 class="text-2xl">Explore Nature</h2>
|
86
|
-
* <p>Discover the beauty of the outdoors</p>
|
87
|
-
* </div>
|
88
|
-
* </div>
|
89
|
-
* </GridLayers>
|
90
|
-
* ```
|
91
|
-
*/
|
92
|
-
|
93
|
-
/**
|
94
|
-
* @type {{
|
95
|
-
* base?: string,
|
96
|
-
* bg?: string,
|
97
|
-
* padding?: string,
|
98
|
-
* margin?: string,
|
99
|
-
* height?: string,
|
100
|
-
* classes?: string,
|
101
|
-
* style?: string,
|
102
|
-
* cellBase?: string,
|
103
|
-
* cellBg?: string,
|
104
|
-
* cellPadding?: string,
|
105
|
-
* cellMargin?: string,
|
106
|
-
* cellClasses?: string,
|
107
|
-
* cellStyle?: string,
|
108
|
-
* children: import('svelte').Snippet,
|
109
|
-
* cellAttrs?: { [attr: string]: * },
|
110
|
-
* [attr: string]: any
|
111
|
-
* }}
|
112
|
-
*/
|
113
|
-
const {
|
114
|
-
// Style
|
115
|
-
base,
|
116
|
-
bg,
|
117
|
-
padding,
|
118
|
-
margin,
|
119
|
-
height,
|
120
|
-
classes,
|
121
|
-
style,
|
122
|
-
cellBase,
|
123
|
-
cellBg,
|
124
|
-
cellPadding,
|
125
|
-
cellMargin,
|
126
|
-
cellClasses,
|
127
|
-
cellStyle,
|
128
|
-
|
129
|
-
cellAttrs,
|
130
|
-
|
131
|
-
// Snippets
|
132
|
-
children,
|
133
|
-
|
134
|
-
// Attributes
|
135
|
-
...attrs
|
136
|
-
} = $props();
|
137
|
-
</script>
|
138
|
-
|
139
|
-
<div
|
140
|
-
data-component="grid-layers"
|
141
|
-
class="relative {base} {bg} {height} {classes} {margin} {padding}"
|
142
|
-
{style}
|
143
|
-
{...attrs}
|
144
|
-
>
|
145
|
-
<div
|
146
|
-
data-section="grid"
|
147
|
-
class="absolute inset-0 grid {cellBase} {cellBg} {cellPadding} {cellMargin} {cellClasses}"
|
148
|
-
style={cellStyle}
|
149
|
-
>
|
150
|
-
{@render children()}
|
151
|
-
</div>
|
152
|
-
</div>
|
153
|
-
|
154
|
-
<style>
|
155
|
-
/* All children of the layer share the same grid area
|
156
|
-
but aren't absolutely positioned */
|
157
|
-
[data-section='grid'] {
|
158
|
-
grid-template-columns: 1fr;
|
159
|
-
grid-template-rows: 1fr;
|
160
|
-
}
|
161
|
-
|
162
|
-
[data-section='grid'] > :global(*) {
|
163
|
-
grid-column: 1;
|
164
|
-
grid-row: 1;
|
165
|
-
z-index: 0; /* Base z-index to allow explicit stacking order */
|
166
|
-
}
|
167
|
-
</style>
|