@hkdigital/lib-sveltekit 0.1.17 → 0.1.18

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.
Files changed (23) hide show
  1. package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte +146 -0
  2. package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte.d.ts +6 -0
  3. package/dist/components/debug/index.d.ts +1 -0
  4. package/dist/components/debug/index.js +1 -0
  5. package/dist/components/layout/HkAppLayout.svelte +3 -3
  6. package/dist/components/layout/game-box/GameBox.svelte +167 -0
  7. package/dist/components/layout/game-box/GameBox.svelte.d.ts +80 -0
  8. package/dist/components/layout/grid-layers/GridLayers.svelte +167 -0
  9. package/dist/components/layout/{GridLayersLayout.svelte.d.ts → grid-layers/GridLayers.svelte.d.ts} +3 -3
  10. package/dist/components/layout/index.d.ts +3 -1
  11. package/dist/components/layout/index.js +3 -1
  12. package/dist/util/design-system/layout/scaling.d.ts +54 -0
  13. package/dist/util/design-system/layout/scaling.js +131 -0
  14. package/package.json +1 -1
  15. package/dist/components/boxes/game-box/GameBox.svelte +0 -112
  16. package/dist/components/boxes/game-box/GameBox.svelte.d.ts +0 -28
  17. package/dist/components/boxes/index.d.ts +0 -2
  18. package/dist/components/boxes/index.js +0 -2
  19. package/dist/components/layout/GridLayersLayout.svelte +0 -89
  20. /package/dist/components/{boxes → layout}/game-box/gamebox.util.d.ts +0 -0
  21. /package/dist/components/{boxes → layout}/game-box/gamebox.util.js +0 -0
  22. /package/dist/components/{boxes → layout}/virtual-viewport/VirtualViewport.svelte +0 -0
  23. /package/dist/components/{boxes → layout}/virtual-viewport/VirtualViewport.svelte.d.ts +0 -0
@@ -0,0 +1,146 @@
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+ import { DESIGN, CLAMPING } from '../../../design/design-config.js';
4
+ import {
5
+ enableScalingUI,
6
+ getAllRootScalingVars
7
+ } from '../../../util/design-system/index.js';
8
+
9
+ /**
10
+ * Holds the current scaling values for the debug panel
11
+ * @type {Object}
12
+ */
13
+ let scalingValues = $state({
14
+ scaleW: 0,
15
+ scaleH: 0,
16
+ scaleViewport: 0,
17
+ scaleUI: 0,
18
+ scaleTextBase: 0,
19
+ scaleTextHeading: 0,
20
+ scaleTextUI: 0
21
+ });
22
+
23
+ /**
24
+ * Controls visibility of the debug panel
25
+ */
26
+ let showDebugPanel = $state(false);
27
+
28
+ /**
29
+ * Updates scaling values for the debug panel
30
+ */
31
+ function updateDebugValues() {
32
+ // Get scaling values
33
+ scalingValues = getAllRootScalingVars();
34
+
35
+ // Update window size display if in browser context
36
+ if (typeof window !== 'undefined') {
37
+ const windowSizeElement = document.getElementById('window-size');
38
+ if (windowSizeElement) {
39
+ windowSizeElement.textContent = `${window.innerWidth}×${window.innerHeight}px`;
40
+ }
41
+ }
42
+ }
43
+
44
+ onMount(() => {
45
+ // Initialize the design scaling system
46
+ //const cleanup = () => {};
47
+ const cleanup = enableScalingUI(DESIGN, CLAMPING);
48
+
49
+ // Update debug values initially
50
+ updateDebugValues();
51
+
52
+ // Set up event listener for updating debug values on resize
53
+ window.addEventListener('resize', updateDebugValues);
54
+
55
+ // Return combined cleanup function
56
+ return () => {
57
+ cleanup();
58
+ window.removeEventListener('resize', updateDebugValues);
59
+ };
60
+ });
61
+
62
+ /**
63
+ * Toggle debug panel visibility
64
+ */
65
+ function toggleDebugPanel() {
66
+ showDebugPanel = !showDebugPanel;
67
+ }
68
+
69
+ /**
70
+ * Format a number to 2 decimal places
71
+ * @param {number} value - The number to format
72
+ * @returns {string} Formatted number
73
+ */
74
+ function formatNumber(value) {
75
+ return value.toFixed(2);
76
+ }
77
+ </script>
78
+
79
+ <!-- Debug Panel -->
80
+ {#if showDebugPanel}
81
+ <div
82
+ class="fixed bottom-0 right-0 bg-black bg-opacity-75 text-white p-2 text-ui-md z-50 font-mono"
83
+ >
84
+ <div class="flex justify-between items-center mb-1">
85
+ <h3 class="font-bold">Design System Scaling</h3>
86
+ <button
87
+ onclick={toggleDebugPanel}
88
+ class="ml-2 px-1.5 bg-gray-700 hover:bg-gray-600 rounded"
89
+ >
90
+ &times;
91
+ </button>
92
+ </div>
93
+ <div class="grid grid-cols-2 gap-x-2 gap-y-0.5">
94
+ <div class="text-gray-400">Design Width:</div>
95
+ <div>{DESIGN.width}px</div>
96
+
97
+ <div class="text-gray-400">Design Height:</div>
98
+ <div>{DESIGN.height}px</div>
99
+
100
+ <div class="text-gray-400">Window:</div>
101
+ <div id="window-size">...</div>
102
+
103
+ <div class="text-gray-400">Scale W:</div>
104
+ <div>{formatNumber(scalingValues.scaleW)}</div>
105
+
106
+ <div class="text-gray-400">Scale H:</div>
107
+ <div>{formatNumber(scalingValues.scaleH)}</div>
108
+
109
+ <div class="text-gray-400">Viewport:</div>
110
+ <div>{formatNumber(scalingValues.scaleViewport)}</div>
111
+
112
+ <div class="text-gray-400">UI:</div>
113
+ <div>{formatNumber(scalingValues.scaleUI)}</div>
114
+
115
+ <div class="text-gray-400">Text Content:</div>
116
+ <div>{formatNumber(scalingValues.scaleTextBase)}</div>
117
+
118
+ <div class="text-gray-400">Text Heading:</div>
119
+ <div>{formatNumber(scalingValues.scaleTextHeading)}</div>
120
+
121
+ <div class="text-gray-400">Text UI:</div>
122
+ <div>{formatNumber(scalingValues.scaleTextUI)}</div>
123
+ </div>
124
+
125
+ <div class="mt-1 pt-1 border-t border-gray-600 text-gray-400">
126
+ <div>Clamping:</div>
127
+ <div class="grid grid-cols-3 text-2xs">
128
+ <div>UI: {CLAMPING.ui.min} - {CLAMPING.ui.max}</div>
129
+ <div>
130
+ Content: {CLAMPING.textBase.min} - {CLAMPING.textBase.max}
131
+ </div>
132
+ <div>
133
+ Heading: {CLAMPING.textHeading.min} - {CLAMPING.textHeading.max}
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ {:else}
139
+ <!-- Debug Panel Toggle Button -->
140
+ <button
141
+ onclick={toggleDebugPanel}
142
+ class="fixed bottom-0 right-0 bg-black bg-opacity-75 text-white p-16ut py-8ut text-ui-md z-50 font-mono hover:bg-opacity-90"
143
+ >
144
+ Show Debug
145
+ </button>
146
+ {/if}
@@ -0,0 +1,6 @@
1
+ export default DebugPanelDesignScaling;
2
+ type DebugPanelDesignScaling = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<Record<string, never>>): void;
5
+ };
6
+ declare const DebugPanelDesignScaling: import("svelte").Component<Record<string, never>, {}, "">;
@@ -0,0 +1 @@
1
+ export { default as DebugPanelDesignScaling } from "./debug-panel-design-scaling/DebugPanelDesignScaling.svelte";
@@ -0,0 +1 @@
1
+ export { default as DebugPanelDesignScaling } from './debug-panel-design-scaling/DebugPanelDesignScaling.svelte';
@@ -241,11 +241,11 @@
241
241
  grid-area: 1 / 1 / 2 / 2; /* row-start, col-start, row-end, col-end */
242
242
  }
243
243
 
244
- :global(html:not(.no-scrollbar-shift-fix)) {
245
- /*
244
+ /*
246
245
  * Shift contents of page to the left when scrollbar appears,
247
246
  * so content like tab bars stay centered
248
247
  */
248
+ /*:global(html:not(.no-scrollbar-shift-fix)) {
249
249
  padding-left: calc(100vw - 100%);
250
- }
250
+ }*/
251
251
  </style>
@@ -0,0 +1,167 @@
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+ import {
4
+ getGameWidthOnLandscape,
5
+ getGameWidthOnPortrait
6
+ } from './gamebox.util.js';
7
+ import { enableContainerScaling } from '../../../util/design-system/index.js';
8
+
9
+ /**
10
+ * @type {{
11
+ * base?: string,
12
+ * bg?: string,
13
+ * classes?: string,
14
+ * style?: string,
15
+ * aspectOnLandscape?: number,
16
+ * aspectOnPortrait?: number,
17
+ * enableScaling?: boolean,
18
+ * designLandscape?: {width: number, height: number},
19
+ * designPortrait?: {width: number, height: number},
20
+ * clamping?: {
21
+ * ui: {min: number, max: number},
22
+ * textBase: {min: number, max: number},
23
+ * textHeading: {min: number, max: number},
24
+ * textUi: {min: number, max: number}
25
+ * },
26
+ * snippetLandscape?: import('svelte').Snippet,
27
+ * snippetPortrait?: import('svelte').Snippet,
28
+ * [attr: string]: any
29
+ * }}
30
+ */
31
+ const {
32
+ // > Style
33
+ base = '',
34
+ bg = '',
35
+ classes = '',
36
+ style = '',
37
+
38
+ // > Functional properties
39
+ aspectOnLandscape,
40
+ aspectOnPortrait,
41
+
42
+ // > Scaling options
43
+ enableScaling = false,
44
+ designLandscape = { width: 1920, height: 1080 },
45
+ designPortrait = { width: 1080, height: 1920 },
46
+ clamping = {
47
+ ui: { min: 0.3, max: 2 },
48
+ textBase: { min: 0.75, max: 1.5 },
49
+ textHeading: { min: 0.75, max: 2.25 },
50
+ textUi: { min: 0.5, max: 1.25 }
51
+ },
52
+
53
+ // > Snippets
54
+ snippetLandscape,
55
+ snippetPortrait
56
+ } = $props();
57
+
58
+ // > Game dimensions and state
59
+ let windowWidth = $state();
60
+ let windowHeight = $state();
61
+ let gameWidth = $state();
62
+ let gameHeight = $state();
63
+ let isLandscape = $derived(windowWidth > windowHeight);
64
+
65
+ // Game container reference
66
+ let gameContainer = $state();
67
+
68
+ // Update game dimensions based on window size and orientation
69
+ $effect(() => {
70
+ if (!windowWidth || !windowHeight) return;
71
+
72
+ let gameAspect;
73
+
74
+ if (windowWidth > windowHeight) {
75
+ gameWidth = getGameWidthOnLandscape({
76
+ windowWidth,
77
+ windowHeight,
78
+ aspectOnLandscape
79
+ });
80
+ gameAspect = aspectOnLandscape;
81
+ } else {
82
+ gameWidth = getGameWidthOnPortrait({
83
+ windowWidth,
84
+ windowHeight,
85
+ aspectOnPortrait
86
+ });
87
+ gameAspect = aspectOnPortrait;
88
+ }
89
+
90
+ if (gameAspect) {
91
+ gameHeight = gameWidth / gameAspect;
92
+ } else {
93
+ gameHeight = windowHeight;
94
+ }
95
+ });
96
+
97
+ // Set up scaling if enabled, with orientation awareness
98
+ $effect(() => {
99
+ if (!enableScaling || !gameContainer || !gameWidth || !gameHeight) {
100
+ return () => {}; // No-op cleanup if scaling not enabled or required elements missing
101
+ }
102
+
103
+ // Select the appropriate design based on orientation
104
+ const activeDesign = isLandscape ? designLandscape : designPortrait;
105
+
106
+ // Log to help debug
107
+ console.debug(
108
+ `GameBox scaling [${isLandscape ? 'landscape' : 'portrait'}]:`,
109
+ `game: ${gameWidth}x${gameHeight}`,
110
+ `design: ${activeDesign.width}x${activeDesign.height}`
111
+ );
112
+
113
+ // Apply scaling with the current design based on orientation
114
+ return enableContainerScaling({
115
+ container: gameContainer,
116
+ design: activeDesign,
117
+ clamping,
118
+ getDimensions: () => ({
119
+ width: gameWidth,
120
+ height: gameHeight
121
+ })
122
+ });
123
+ });
124
+
125
+ onMount(() => {
126
+ const gameBoxNoScroll = 'game-box-no-scroll';
127
+ const html = document.documentElement;
128
+ html.classList.add(gameBoxNoScroll);
129
+
130
+ return () => {
131
+ html.classList.remove(gameBoxNoScroll);
132
+ };
133
+ });
134
+ </script>
135
+
136
+ <svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
137
+
138
+ {#if gameHeight}
139
+ <div
140
+ data-component="game-box"
141
+ data-orientation={isLandscape ? 'landscape' : 'portrait'}
142
+ bind:this={gameContainer}
143
+ class="{base} {bg} {classes}"
144
+ style:width="{gameWidth}px"
145
+ style:height="{gameHeight}px"
146
+ style:--game-width={gameWidth}
147
+ style:--game-height={gameHeight}
148
+ {style}
149
+ >
150
+ {#if isLandscape}
151
+ {@render snippetLandscape()}
152
+ {:else}
153
+ {@render snippetPortrait()}
154
+ {/if}
155
+ </div>
156
+ {/if}
157
+
158
+ <style>
159
+ :global(html.game-box-no-scroll) {
160
+ overflow: clip;
161
+ scrollbar-width: none; /* Firefox */
162
+ -ms-overflow-style: none; /* IE and Edge */
163
+ }
164
+ :global(html.game-box-no-scroll::-webkit-scrollbar) {
165
+ display: none;
166
+ }
167
+ </style>
@@ -0,0 +1,80 @@
1
+ export default GameBox;
2
+ type GameBox = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<{
5
+ [attr: string]: any;
6
+ base?: string;
7
+ bg?: string;
8
+ classes?: string;
9
+ style?: string;
10
+ aspectOnLandscape?: number;
11
+ aspectOnPortrait?: number;
12
+ enableScaling?: boolean;
13
+ designLandscape?: {
14
+ width: number;
15
+ height: number;
16
+ };
17
+ designPortrait?: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ clamping?: {
22
+ ui: {
23
+ min: number;
24
+ max: number;
25
+ };
26
+ textBase: {
27
+ min: number;
28
+ max: number;
29
+ };
30
+ textHeading: {
31
+ min: number;
32
+ max: number;
33
+ };
34
+ textUi: {
35
+ min: number;
36
+ max: number;
37
+ };
38
+ };
39
+ snippetLandscape?: Snippet<[]>;
40
+ snippetPortrait?: Snippet<[]>;
41
+ }>): void;
42
+ };
43
+ declare const GameBox: import("svelte").Component<{
44
+ [attr: string]: any;
45
+ base?: string;
46
+ bg?: string;
47
+ classes?: string;
48
+ style?: string;
49
+ aspectOnLandscape?: number;
50
+ aspectOnPortrait?: number;
51
+ enableScaling?: boolean;
52
+ designLandscape?: {
53
+ width: number;
54
+ height: number;
55
+ };
56
+ designPortrait?: {
57
+ width: number;
58
+ height: number;
59
+ };
60
+ clamping?: {
61
+ ui: {
62
+ min: number;
63
+ max: number;
64
+ };
65
+ textBase: {
66
+ min: number;
67
+ max: number;
68
+ };
69
+ textHeading: {
70
+ min: number;
71
+ max: number;
72
+ };
73
+ textUi: {
74
+ min: number;
75
+ max: number;
76
+ };
77
+ };
78
+ snippetLandscape?: import("svelte").Snippet;
79
+ snippetPortrait?: import("svelte").Snippet;
80
+ }, {}, "">;
@@ -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="layer"
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='layer'] {
158
+ grid-template-columns: 1fr;
159
+ grid-template-rows: 1fr;
160
+ }
161
+
162
+ [data-section='layer'] > :global(*) {
163
+ grid-column: 1;
164
+ grid-row: 1;
165
+ z-index: 0; /* Base z-index to allow explicit stacking order */
166
+ }
167
+ </style>
@@ -1,5 +1,5 @@
1
- export default GridLayersLayout;
2
- type GridLayersLayout = {
1
+ export default GridLayers;
2
+ type GridLayers = {
3
3
  $on?(type: string, callback: (e: any) => void): () => void;
4
4
  $set?(props: Partial<{
5
5
  [attr: string]: any;
@@ -22,7 +22,7 @@ type GridLayersLayout = {
22
22
  };
23
23
  }>): void;
24
24
  };
25
- declare const GridLayersLayout: import("svelte").Component<{
25
+ declare const GridLayers: import("svelte").Component<{
26
26
  [attr: string]: any;
27
27
  base?: string;
28
28
  bg?: string;
@@ -1,3 +1,5 @@
1
1
  export { default as HkAppLayout } from "./HkAppLayout.svelte";
2
- export { default as GridLayersLayout } from "./GridLayersLayout.svelte";
2
+ export { default as GameBox } from "./game-box/GameBox.svelte";
3
+ export { default as GridLayers } from "./grid-layers/GridLayers.svelte";
4
+ export { default as VirtualViewport } from "./virtual-viewport/VirtualViewport.svelte";
3
5
  export { createOrGetState as createOrGetAppLayoutState, createState as createAppLayoutState, getState as getAppLayoutState } from "./HkAppLayout.state.svelte.js";
@@ -6,4 +6,6 @@ export {
6
6
 
7
7
  export { default as HkAppLayout } from './HkAppLayout.svelte';
8
8
 
9
- export { default as GridLayersLayout } from './GridLayersLayout.svelte';
9
+ export { default as GameBox } from './game-box/GameBox.svelte';
10
+ export { default as GridLayers } from './grid-layers/GridLayers.svelte';
11
+ export { default as VirtualViewport } from './virtual-viewport/VirtualViewport.svelte';
@@ -1,3 +1,57 @@
1
+ /**
2
+ * Manages responsive design scaling by calculating and applying scale factors
3
+ * based on container dimensions and design system requirements.
4
+ *
5
+ * @param {Object} options - Configuration options
6
+ * @param {HTMLElement} options.container - The container element to apply scaling to
7
+ * @param {Object} options.design - The base design dimensions
8
+ * @param {number} options.design.width - The reference design width
9
+ * @param {number} options.design.height - The reference design height
10
+ * @param {Object} options.clamping - The min/max values for various scale factors
11
+ * @param {Object} options.clamping.ui - UI element scaling constraints
12
+ * @param {number} options.clamping.ui.min - Minimum UI scale factor
13
+ * @param {number} options.clamping.ui.max - Maximum UI scale factor
14
+ * @param {Object} options.clamping.textBase - Base text scaling constraints
15
+ * @param {number} options.clamping.textBase.min - Minimum base text scale factor
16
+ * @param {number} options.clamping.textBase.max - Maximum base text scale factor
17
+ * @param {Object} options.clamping.textHeading - Heading text scaling constraints
18
+ * @param {number} options.clamping.textHeading.min - Minimum heading text scale factor
19
+ * @param {number} options.clamping.textHeading.max - Maximum heading text scale factor
20
+ * @param {Object} options.clamping.textUi - UI text scaling constraints
21
+ * @param {number} options.clamping.textUi.min - Minimum UI text scale factor
22
+ * @param {number} options.clamping.textUi.max - Maximum UI text scale factor
23
+ * @param {Function} [options.getDimensions] - Optional function to get width and height
24
+ * @param {boolean} [options.useResizeObserver=true] - Whether to use ResizeObserver
25
+ *
26
+ * @returns {()=>void} A cleanup function that removes event listeners and observers
27
+ */
28
+ export function enableContainerScaling({ container, design, clamping, getDimensions, useResizeObserver }: {
29
+ container: HTMLElement;
30
+ design: {
31
+ width: number;
32
+ height: number;
33
+ };
34
+ clamping: {
35
+ ui: {
36
+ min: number;
37
+ max: number;
38
+ };
39
+ textBase: {
40
+ min: number;
41
+ max: number;
42
+ };
43
+ textHeading: {
44
+ min: number;
45
+ max: number;
46
+ };
47
+ textUi: {
48
+ min: number;
49
+ max: number;
50
+ };
51
+ };
52
+ getDimensions?: Function;
53
+ useResizeObserver?: boolean;
54
+ }): () => void;
1
55
  /**
2
56
  * Manages responsive design scaling by calculating and applying scale factors
3
57
  * based on viewport dimensions and design system requirements.
@@ -1,5 +1,136 @@
1
1
  import { clamp } from '../css/clamp.js';
2
2
 
3
+ /**
4
+ * Manages responsive design scaling by calculating and applying scale factors
5
+ * based on container dimensions and design system requirements.
6
+ *
7
+ * @param {Object} options - Configuration options
8
+ * @param {HTMLElement} options.container - The container element to apply scaling to
9
+ * @param {Object} options.design - The base design dimensions
10
+ * @param {number} options.design.width - The reference design width
11
+ * @param {number} options.design.height - The reference design height
12
+ * @param {Object} options.clamping - The min/max values for various scale factors
13
+ * @param {Object} options.clamping.ui - UI element scaling constraints
14
+ * @param {number} options.clamping.ui.min - Minimum UI scale factor
15
+ * @param {number} options.clamping.ui.max - Maximum UI scale factor
16
+ * @param {Object} options.clamping.textBase - Base text scaling constraints
17
+ * @param {number} options.clamping.textBase.min - Minimum base text scale factor
18
+ * @param {number} options.clamping.textBase.max - Maximum base text scale factor
19
+ * @param {Object} options.clamping.textHeading - Heading text scaling constraints
20
+ * @param {number} options.clamping.textHeading.min - Minimum heading text scale factor
21
+ * @param {number} options.clamping.textHeading.max - Maximum heading text scale factor
22
+ * @param {Object} options.clamping.textUi - UI text scaling constraints
23
+ * @param {number} options.clamping.textUi.min - Minimum UI text scale factor
24
+ * @param {number} options.clamping.textUi.max - Maximum UI text scale factor
25
+ * @param {Function} [options.getDimensions] - Optional function to get width and height
26
+ * @param {boolean} [options.useResizeObserver=true] - Whether to use ResizeObserver
27
+ *
28
+ * @returns {()=>void} A cleanup function that removes event listeners and observers
29
+ */
30
+ export function enableContainerScaling({
31
+ container,
32
+ design,
33
+ clamping,
34
+ getDimensions,
35
+ useResizeObserver = true
36
+ }) {
37
+ if (!container) {
38
+ throw new Error('Container element is required for enableContainerScaling');
39
+ }
40
+
41
+ let resizeObserver;
42
+
43
+ /**
44
+ * Updates CSS scale variables based on container dimensions
45
+ * and design system constraints
46
+ */
47
+ function updateScaleValues() {
48
+ try {
49
+ let containerWidth, containerHeight;
50
+
51
+ // Use custom dimension getter if provided
52
+ if (typeof getDimensions === 'function') {
53
+ const dimensions = getDimensions();
54
+ containerWidth = dimensions.width;
55
+ containerHeight = dimensions.height;
56
+ } else {
57
+ // Otherwise use container's client dimensions
58
+ const rect = container.getBoundingClientRect();
59
+ containerWidth = rect.width;
60
+ containerHeight = rect.height;
61
+ }
62
+
63
+ // Skip update if dimensions are zero (container not visible)
64
+ if (containerWidth <= 0 || containerHeight <= 0) {
65
+ return;
66
+ }
67
+
68
+ // Calculate scale factors based on container size relative to design dimensions
69
+ const scaleW = containerWidth / design.width;
70
+ const scaleH = containerHeight / design.height;
71
+
72
+ // Use the smaller scale factor to ensure content fits within container
73
+ const scaleViewport = Math.min(scaleW, scaleH);
74
+
75
+ // Apply clamping to different element types
76
+ const scaleUI = clamp(clamping.ui.min, scaleViewport, clamping.ui.max);
77
+
78
+ const scaleTextBase = clamp(
79
+ clamping.textBase.min,
80
+ scaleViewport,
81
+ clamping.textBase.max
82
+ );
83
+
84
+ const scaleTextHeading = clamp(
85
+ clamping.textHeading.min,
86
+ scaleViewport,
87
+ clamping.textHeading.max
88
+ );
89
+
90
+ const scaleTextUi = clamp(
91
+ clamping.textUi.min,
92
+ scaleViewport,
93
+ clamping.textUi.max
94
+ );
95
+
96
+ // Set CSS custom properties on the container
97
+ container.style.setProperty('--scale-w', String(scaleW));
98
+ container.style.setProperty('--scale-h', String(scaleH));
99
+ container.style.setProperty('--scale-viewport', String(scaleViewport));
100
+ container.style.setProperty('--scale-ui', String(scaleUI));
101
+ container.style.setProperty('--scale-text-base', String(scaleTextBase));
102
+ container.style.setProperty(
103
+ '--scale-text-heading',
104
+ String(scaleTextHeading)
105
+ );
106
+ container.style.setProperty('--scale-text-ui', String(scaleTextUi));
107
+ } catch (error) {
108
+ console.error('Error updating container scale values:', error);
109
+ }
110
+ }
111
+
112
+ // Initialize scales
113
+ updateScaleValues();
114
+
115
+ // Set up ResizeObserver for container resize detection
116
+ if (useResizeObserver && typeof ResizeObserver !== 'undefined') {
117
+ resizeObserver = new ResizeObserver(updateScaleValues);
118
+ resizeObserver.observe(container);
119
+ } else {
120
+ // Fallback to window resize event
121
+ window.addEventListener('resize', updateScaleValues);
122
+ }
123
+
124
+ // Return cleanup function
125
+ return function cleanup() {
126
+ if (resizeObserver) {
127
+ resizeObserver.disconnect();
128
+ } else {
129
+ window.removeEventListener('resize', updateScaleValues);
130
+ }
131
+ };
132
+ }
133
+
3
134
  /**
4
135
  * Manages responsive design scaling by calculating and applying scale factors
5
136
  * based on viewport dimensions and design system requirements.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-sveltekit",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1,112 +0,0 @@
1
- <script>
2
- import {
3
- getGameWidthOnLandscape,
4
- getGameWidthOnPortrait
5
- } from './gamebox.util.js';
6
-
7
- /**
8
- * @type {{
9
- * base?: string,
10
- * bg?: string,
11
- * classes?: string,
12
- * style?: string,
13
- * aspectOnLandscape? :number,
14
- * aspectOnPortrait? :number,
15
- * onLandscape?: import('svelte').Snippet,
16
- * onPortrait?: import('svelte').Snippet
17
- * } & { [attr: string]: any }}
18
- */
19
- const {
20
- // > Style
21
- base,
22
- bg,
23
- classes,
24
- style,
25
-
26
- // > Functional
27
- aspectOnLandscape,
28
- aspectOnPortrait,
29
-
30
- // >Snippets
31
- snippetLandscape,
32
- snippetPortrait
33
- } = $props();
34
-
35
- // > Game width and height
36
-
37
- let windowWidth = $state();
38
- let windowHeight = $state();
39
-
40
- let gameWidth = $state();
41
- let gameHeight = $state();
42
-
43
- let isLandscape = $derived(gameWidth > gameHeight);
44
-
45
- $effect(() => {
46
- // Determine game width and height from
47
- // window dimensions and desired game aspect
48
-
49
- let gameAspect;
50
-
51
- if (windowWidth > windowHeight) {
52
- gameWidth = getGameWidthOnLandscape({
53
- windowWidth,
54
- windowHeight,
55
- aspectOnLandscape
56
- });
57
-
58
- gameAspect = aspectOnLandscape;
59
- } else {
60
- gameWidth = getGameWidthOnPortrait({
61
- windowWidth,
62
- windowHeight,
63
- aspectOnPortrait
64
- });
65
-
66
- gameAspect = aspectOnPortrait;
67
- }
68
-
69
- if (gameAspect) {
70
- gameHeight = gameWidth / gameAspect;
71
- } else {
72
- gameHeight = windowHeight;
73
- }
74
- });
75
-
76
- // $inspect({ windowWidth, windowHeight, gameWidth, gameHeight, isLandscape });
77
-
78
- // $effect(() => {
79
- // console.log({
80
- // windowWidth,
81
- // windowHeight,
82
- // gameWidth,
83
- // gameHeight,
84
- // isLandscape
85
- // });
86
- // });
87
- </script>
88
-
89
- <svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
90
-
91
- {#if gameHeight}
92
- <!-- <div
93
- data-boxes="game-box"
94
- class="{base} {bg} {classes}"
95
- style="width: {gameWidth}px; height: {gameHeight}px;--game-width={gameWidth};--game-height={gameHeight}; {style}" -->
96
-
97
- <div
98
- data-boxes="game-box"
99
- class="{base} {bg} {classes}"
100
- style:width="{gameWidth}px"
101
- style:height="{gameHeight}px"
102
- style:--game-width={gameWidth}
103
- style:--game-height={gameHeight}
104
- {style}
105
- >
106
- {#if isLandscape}
107
- {@render snippetLandscape()}
108
- {:else}
109
- {@render snippetPortrait()}
110
- {/if}
111
- </div>
112
- {/if}
@@ -1,28 +0,0 @@
1
- export default GameBox;
2
- type GameBox = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<{
5
- base?: string;
6
- bg?: string;
7
- classes?: string;
8
- style?: string;
9
- aspectOnLandscape?: number;
10
- aspectOnPortrait?: number;
11
- onLandscape?: Snippet<[]>;
12
- onPortrait?: Snippet<[]>;
13
- } & {
14
- [attr: string]: any;
15
- }>): void;
16
- };
17
- declare const GameBox: import("svelte").Component<{
18
- base?: string;
19
- bg?: string;
20
- classes?: string;
21
- style?: string;
22
- aspectOnLandscape?: number;
23
- aspectOnPortrait?: number;
24
- onLandscape?: import("svelte").Snippet;
25
- onPortrait?: import("svelte").Snippet;
26
- } & {
27
- [attr: string]: any;
28
- }, {}, "">;
@@ -1,2 +0,0 @@
1
- export { default as GameBox } from "./game-box/GameBox.svelte";
2
- export { default as VirtualViewport } from "./virtual-viewport/VirtualViewport.svelte";
@@ -1,2 +0,0 @@
1
- export { default as GameBox } from './game-box/GameBox.svelte';
2
- export { default as VirtualViewport } from './virtual-viewport/VirtualViewport.svelte';
@@ -1,89 +0,0 @@
1
- <script>
2
- /**
3
- * Grid Layers component
4
- * This is a grid with only one cell. All direct children are
5
- * place in the same cell and form a visually stacked component.
6
- *
7
- * This can be used to place e.g. texts over an image. Place the
8
- * image in a layer and the text in another. The standard grid
9
- * content placement options can be used.
10
- *
11
- * Following component guidelines from Skeleton
12
- * @see https://next.skeleton.dev/docs/resources/contribute/components
13
- */
14
-
15
- /**
16
- * @type {{
17
- * base?: string,
18
- * bg?: string,
19
- * padding?: string,
20
- * margin?: string,
21
- * height?: string,
22
- * classes?: string,
23
- * style?: string,
24
- * cellBase?: string,
25
- * cellBg?: string,
26
- * cellPadding?: string,
27
- * cellMargin?: string,
28
- * cellClasses?: string,
29
- * cellStyle?: string,
30
- * children: import('svelte').Snippet,
31
- * cellAttrs?: { [attr: string]: * },
32
- * [attr: string]: any
33
- * }}
34
- */
35
- const {
36
- // Style
37
- base,
38
- bg,
39
- padding,
40
- margin,
41
- height,
42
- classes,
43
- style,
44
- cellBase,
45
- cellBg,
46
- cellPadding,
47
- cellMargin,
48
- cellClasses,
49
- cellStyle,
50
-
51
- cellAttrs,
52
-
53
- // Snippets
54
- children,
55
-
56
- // Attributes
57
- ...attrs
58
- } = $props();
59
- </script>
60
-
61
- <div
62
- data-component="grid-layers"
63
- class="relative {base} {bg} {height} {classes} {margin} {padding}"
64
- {style}
65
- {...attrs}
66
- >
67
- <div
68
- data-section="layer"
69
- class="absolute inset-0 grid {cellBase} {cellBg} {cellPadding} {cellMargin} {cellClasses}"
70
- style={cellStyle}
71
- >
72
- {@render children()}
73
- </div>
74
- </div>
75
-
76
- <style>
77
- /* All children of the layer share the same grid area
78
- but aren't absolutely positioned */
79
- [data-section='layer'] {
80
- grid-template-columns: 1fr;
81
- grid-template-rows: 1fr;
82
- }
83
-
84
- [data-section='layer'] > :global(*) {
85
- grid-column: 1;
86
- grid-row: 1;
87
- z-index: 0; /* Base z-index to allow explicit stacking order */
88
- }
89
- </style>