@hkdigital/lib-sveltekit 0.1.80 → 0.1.82

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.
@@ -1,167 +1,186 @@
1
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();
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import { setupLayerObserver, measureTargetLayer } from './util.js';
4
+
5
+ /**
6
+ * @type {{
7
+ * base?: string,
8
+ * bg?: string,
9
+ * padding?: string,
10
+ * margin?: string,
11
+ * height?: string,
12
+ * classes?: string,
13
+ * style?: string,
14
+ * cellBase?: string,
15
+ * cellBg?: string,
16
+ * cellPadding?: string,
17
+ * cellMargin?: string,
18
+ * cellClasses?: string,
19
+ * cellStyle?: string,
20
+ * heightFrom?: string|null,
21
+ * children: import('svelte').Snippet,
22
+ * cellAttrs?: { [attr: string]: any },
23
+ * [attr: string]: any
24
+ * }}
25
+ */
26
+ const {
27
+ // Style
28
+ base = '',
29
+ bg = '',
30
+ padding = '',
31
+ margin = '',
32
+ height = '',
33
+ classes = '',
34
+ style = '',
35
+ cellBase = '',
36
+ cellBg = '',
37
+ cellPadding = '',
38
+ cellMargin = '',
39
+ cellClasses = '',
40
+ cellStyle = '',
41
+
42
+ // Behavior
43
+ heightFrom = null,
44
+
45
+ // Props
46
+ cellAttrs = {},
47
+ children,
48
+
49
+ // Attributes
50
+ ...attrs
51
+ } = $props();
52
+
53
+ // Component state
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
63
+ let containerStyle = $derived.by(() => {
64
+ const styles = [];
65
+
66
+ if (style) {
67
+ styles.push(style);
68
+ }
69
+
70
+ if (heightFrom && calculatedHeight > 0) {
71
+ styles.push(`height: ${calculatedHeight}px;`);
72
+ }
73
+
74
+ return styles.join(' ');
75
+ });
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
+ });
137
153
  </script>
138
154
 
139
155
  <div
140
- data-component="grid-layers"
141
- class="relative {base} {bg} {height} {classes} {margin} {padding}"
142
- {style}
143
- {...attrs}
156
+ data-component="grid-layers"
157
+ bind:this={gridContainer}
158
+ class="relative {isFirstRender ? 'invisible' : ''} {base} {bg} {!heightFrom ? height : ''} {classes} {margin} {padding}"
159
+ style={containerStyle}
160
+ {...attrs}
144
161
  >
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>
162
+ <div
163
+ data-section="grid"
164
+ bind:this={gridContent}
165
+ class="absolute inset-0 grid {cellBase} {cellBg} {cellPadding} {cellMargin} {cellClasses}"
166
+ style={cellStyle}
167
+ {...cellAttrs}
168
+ >
169
+ {@render children()}
170
+ </div>
152
171
  </div>
153
172
 
154
173
  <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
- }
174
+ /* All children of the layer share the same grid area
175
+ but aren't absolutely positioned */
176
+ [data-section='grid'] {
177
+ grid-template-columns: 1fr;
178
+ grid-template-rows: 1fr;
179
+ }
180
+
181
+ [data-section='grid'] > :global(*) {
182
+ grid-column: 1;
183
+ grid-row: 1;
184
+ z-index: 0; /* Base z-index to allow explicit stacking order */
185
+ }
167
186
  </style>
@@ -16,6 +16,7 @@ type GridLayers = {
16
16
  cellMargin?: string;
17
17
  cellClasses?: string;
18
18
  cellStyle?: string;
19
+ heightFrom?: string;
19
20
  children: Snippet<[]>;
20
21
  cellAttrs?: {
21
22
  [attr: string]: any;
@@ -37,6 +38,7 @@ declare const GridLayers: import("svelte").Component<{
37
38
  cellMargin?: string;
38
39
  cellClasses?: string;
39
40
  cellStyle?: string;
41
+ heightFrom?: string | null;
40
42
  children: import("svelte").Snippet;
41
43
  cellAttrs?: {
42
44
  [attr: string]: any;
@@ -0,0 +1,167 @@
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>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Sets up a ResizeObserver on the target layer
3
+ *
4
+ * @param {HTMLElement|null} targetLayer - The layer element to observe
5
+ * @param {Function} onHeightChange - Callback when height changes
6
+ * @returns {ResizeObserver|null} - The created observer or null
7
+ */
8
+ export function setupLayerObserver(targetLayer: HTMLElement | null, onHeightChange: Function): ResizeObserver | null;
9
+ /**
10
+ * Measures the height of the specified layer, including margins
11
+ *
12
+ * @param {HTMLElement|null} container - The container to search in
13
+ * @param {string} layerId - The data-layer attribute value to find
14
+ * @returns {{ element: HTMLElement|null, height: number }} The element and its height
15
+ */
16
+ export function measureTargetLayer(container: HTMLElement | null, layerId: string): {
17
+ element: HTMLElement | null;
18
+ height: number;
19
+ };
@@ -0,0 +1,74 @@
1
+ // lib/components/layout/gridLayers.utils.js
2
+
3
+ /**
4
+ * Sets up a ResizeObserver on the target layer
5
+ *
6
+ * @param {HTMLElement|null} targetLayer - The layer element to observe
7
+ * @param {Function} onHeightChange - Callback when height changes
8
+ * @returns {ResizeObserver|null} - The created observer or null
9
+ */
10
+ export function setupLayerObserver(targetLayer, onHeightChange) {
11
+ if (!targetLayer || !window.ResizeObserver) return null;
12
+
13
+ // Create new observer
14
+ const observer = new ResizeObserver(entries => {
15
+ for (const entry of entries) {
16
+ if (entry.target === targetLayer) {
17
+ // Get the computed style to access margin values
18
+ const computedStyle = window.getComputedStyle(targetLayer);
19
+ const marginTop = parseInt(computedStyle.marginTop, 10);
20
+ const marginBottom = parseInt(computedStyle.marginBottom, 10);
21
+
22
+ // Calculate height including border box and margins
23
+ let elementHeight = 0;
24
+
25
+ if (entry.borderBoxSize) {
26
+ const borderBoxSize = Array.isArray(entry.borderBoxSize)
27
+ ? entry.borderBoxSize[0]
28
+ : entry.borderBoxSize;
29
+ elementHeight = borderBoxSize.blockSize;
30
+ } else {
31
+ // Fallback to getBoundingClientRect
32
+ const rect = targetLayer.getBoundingClientRect();
33
+ elementHeight = rect.height;
34
+ }
35
+
36
+ // Add margins to the height
37
+ const totalHeight = elementHeight + marginTop + marginBottom;
38
+ onHeightChange(totalHeight);
39
+ }
40
+ }
41
+ });
42
+
43
+ // Start observing
44
+ observer.observe(targetLayer);
45
+ return observer;
46
+ }
47
+
48
+ /**
49
+ * Measures the height of the specified layer, including margins
50
+ *
51
+ * @param {HTMLElement|null} container - The container to search in
52
+ * @param {string} layerId - The data-layer attribute value to find
53
+ * @returns {{ element: HTMLElement|null, height: number }} The element and its height
54
+ */
55
+ export function measureTargetLayer(container, layerId) {
56
+ if (!layerId || !container) return { element: null, height: 0 };
57
+
58
+ const layerElement = container.querySelector(`[data-layer="${layerId}"]`);
59
+
60
+ if (!layerElement) return { element: null, height: 0 };
61
+
62
+ // Get the computed style to access margin values
63
+ const computedStyle = window.getComputedStyle(layerElement);
64
+ const marginTop = parseInt(computedStyle.marginTop, 10);
65
+ const marginBottom = parseInt(computedStyle.marginBottom, 10);
66
+
67
+ // Get the element's border box height
68
+ const rect = layerElement.getBoundingClientRect();
69
+
70
+ // Calculate total height including margins
71
+ const height = rect.height > 0 ? rect.height + marginTop + marginBottom : 0;
72
+
73
+ return { element: layerElement, height };
74
+ }
@@ -2,5 +2,3 @@ export const READY: "ready";
2
2
  export const DRAG_OVER: "drag-over";
3
3
  export const CAN_DROP: "can-drop";
4
4
  export const CANNOT_DROP: "cannot-drop";
5
- export const DROP_DISABLED: "drop-disabled";
6
- export const ACTIVE_DROP: "active-drop";
@@ -2,5 +2,5 @@ export const READY = 'ready'; // Waiting for drag
2
2
  export const DRAG_OVER = 'drag-over'; // Item dragged over zone
3
3
  export const CAN_DROP = 'can-drop'; // Valid drop target
4
4
  export const CANNOT_DROP = 'cannot-drop'; // Invalid drop target
5
- export const DROP_DISABLED = 'drop-disabled'; // Dropping not allowed
6
- export const ACTIVE_DROP = 'active-drop'; // Currently processing drop
5
+ // export const DROP_DISABLED = 'drop-disabled'; // Dropping not allowed
6
+ // export const ACTIVE_DROP = 'active-drop'; // Currently processing drop
@@ -57,4 +57,8 @@
57
57
  opacity: 1;
58
58
  }
59
59
  }
60
+
61
+ & [data-companion="drag-preview-follower"] {
62
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
63
+ }
60
64
  }