@hkdigital/lib-sveltekit 0.1.61 → 0.1.64

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,11 +1,30 @@
1
1
  <script>
2
2
  import { onMount } from 'svelte';
3
+
3
4
  import {
4
5
  getGameWidthOnLandscape,
5
6
  getGameWidthOnPortrait
6
7
  } from './gamebox.util.js';
8
+
7
9
  import { enableContainerScaling } from '../../util/design-system/index.js';
8
10
 
11
+ /**
12
+ * @typedef {{
13
+ * isMobile:boolean,
14
+ * device:string,
15
+ * isFullscreen:boolean,
16
+ * isDevMode:boolean,
17
+ * requestDevmode:function,
18
+ * requestFullscreen:function,
19
+ * gameWidth: number,
20
+ * gameHeight: number
21
+ * }} SnippetParams
22
+ */
23
+
24
+ /**
25
+ * @typedef {import('svelte').Snippet<[SnippetParams]>} GameBoxSnippet
26
+ */
27
+
9
28
  /**
10
29
  * @type {{
11
30
  * base?: string,
@@ -14,6 +33,11 @@
14
33
  * style?: string,
15
34
  * aspectOnLandscape?: number,
16
35
  * aspectOnPortrait?: number,
36
+ * marginLeft?: number,
37
+ * marginRight?: number,
38
+ * marginTop?: number,
39
+ * marginBottom?: number,
40
+ * center?: boolean,
17
41
  * enableScaling?: boolean,
18
42
  * designLandscape?: {width: number, height: number},
19
43
  * designPortrait?: {width: number, height: number},
@@ -23,8 +47,10 @@
23
47
  * textHeading: {min: number, max: number},
24
48
  * textUi: {min: number, max: number}
25
49
  * },
26
- * snippetLandscape?: import('svelte').Snippet,
27
- * snippetPortrait?: import('svelte').Snippet,
50
+ * snippetLandscape?:GameBoxSnippet,
51
+ * snippetPortrait?: GameBoxSnippet,
52
+ * snippetRequireFullscreen?: GameBoxSnippet,
53
+ * snippetInstallOnHomeScreen?: GameBoxSnippet,
28
54
  * [attr: string]: any
29
55
  * }}
30
56
  */
@@ -45,10 +71,12 @@
45
71
  marginTop = 0,
46
72
  marginBottom = 0,
47
73
 
74
+ center,
75
+
48
76
  // > Scaling options
49
77
  enableScaling = false,
50
78
  designLandscape = { width: 1920, height: 1080 },
51
- designPortrait = { width: 1080, height: 1920 },
79
+ designPortrait = { width: 1920, height: 1080 },
52
80
  clamping = {
53
81
  ui: { min: 0.3, max: 2 },
54
82
  textBase: { min: 0.75, max: 1.5 },
@@ -58,25 +86,55 @@
58
86
 
59
87
  // > Snippets
60
88
  snippetLandscape,
61
- snippetPortrait
89
+ snippetPortrait,
90
+ snippetRequireFullscreen,
91
+ snippetInstallOnHomeScreen
62
92
  } = $props();
63
93
 
64
94
  // > Game dimensions and state
65
95
  let windowWidth = $state();
66
96
  let windowHeight = $state();
97
+
67
98
  let gameWidth = $state();
68
99
  let gameHeight = $state();
69
- let isLandscape = $derived(windowWidth > windowHeight);
100
+
101
+ let isAppleMobile = getIsAppleMobile();
102
+
103
+ let isPwa = $state(false);
104
+
105
+ let device = $state();
106
+
107
+ let iosWindowWidth = $state();
108
+ let iosWindowHeight = $state();
109
+
110
+ function getIsLandscape() {
111
+ if (isPwa && isAppleMobile) {
112
+ return iosWindowWidth > iosWindowHeight;
113
+ } else {
114
+ return windowWidth > windowHeight;
115
+ }
116
+ }
117
+
118
+ let isLandscape = $state();
119
+
120
+ // $derived.by(getIsLandscape);
121
+
122
+ $effect(() => {
123
+ isLandscape = getIsLandscape();
124
+ });
70
125
 
71
126
  // Game container reference
72
127
  let gameContainer = $state();
73
128
 
74
129
  // Update game dimensions based on window size and orientation
75
130
  $effect(() => {
76
- if (!windowWidth || !windowHeight) return;
131
+ const width = iosWindowWidth ?? windowWidth;
132
+ const height = iosWindowHeight ?? windowHeight;
133
+
134
+ if (!width || !height) return;
77
135
 
78
- const availWidth = windowWidth - marginLeft - marginRight;
79
- const availHeight = windowHeight - marginTop - marginBottom;
136
+ const availWidth = width - marginLeft - marginRight;
137
+ const availHeight = height - marginTop - marginBottom;
80
138
 
81
139
  // console.debug('GameBox margins:', {
82
140
  // marginLeft,
@@ -137,6 +195,92 @@
137
195
  });
138
196
  });
139
197
 
198
+ let show = $state(false);
199
+
200
+ let isMobile = $state(false);
201
+
202
+ let isDevMode = $state(false);
203
+
204
+ // Check: always true for home app?
205
+ let isFullscreen = $state(false);
206
+
207
+ let supportsFullscreen = $state(false);
208
+
209
+ onMount(() => {
210
+ supportsFullscreen = document.fullscreenEnabled;
211
+
212
+ isMobile = getIsMobile();
213
+
214
+ device = whatDevice();
215
+
216
+ // Run before show
217
+ isFullscreen = !!document.fullscreenElement;
218
+
219
+ isPwa = window.matchMedia(
220
+ '(display-mode: fullscreen) or (display-mode: standalone)'
221
+ ).matches;
222
+
223
+ isLandscape = getIsLandscape();
224
+
225
+ show = true;
226
+
227
+ function updateIosWidthHeight() {
228
+ // const isPwa = window.matchMedia(
229
+ // '(display-mode: fullscreen) or (display-mode: standalone)'
230
+ // ).matches;
231
+
232
+ if (isPwa && isAppleMobile) {
233
+ const angle = screen.orientation.angle;
234
+
235
+ if (angle === 90 || angle === 270) {
236
+ iosWindowWidth = screen.height;
237
+ iosWindowHeight = screen.width;
238
+ } else {
239
+ iosWindowWidth = screen.width;
240
+ iosWindowHeight = screen.height;
241
+ }
242
+ // console.debug( { iosWindowWidth, iosWindowHeight } );
243
+ }
244
+ }
245
+
246
+ updateIosWidthHeight();
247
+
248
+ function updateOrientation(event) {
249
+ // console.debug('updateOrientation');
250
+ const type = event.target.type;
251
+ const angle = event.target.angle;
252
+
253
+ // isPwa = window.matchMedia(
254
+ // '(display-mode: fullscreen) or (display-mode: standalone)'
255
+ // ).matches;
256
+
257
+ updateIosWidthHeight();
258
+
259
+ console.debug(
260
+ `ScreenOrientation change: ${type}, ${angle} degrees.`,
261
+ isPwa,
262
+ windowWidth,
263
+ windowHeight,
264
+ screen.width,
265
+ screen.height,
266
+ iosWindowWidth,
267
+ iosWindowHeight
268
+ );
269
+
270
+ // if( angle
271
+ }
272
+
273
+ $effect(() => {
274
+ screen.orientation.addEventListener('change', updateOrientation);
275
+
276
+ return () => {
277
+ screen.orientation.removeEventListener('change', updateOrientation);
278
+ };
279
+ });
280
+
281
+ //
282
+ });
283
+
140
284
  onMount(() => {
141
285
  const gameBoxNoScroll = 'game-box-no-scroll';
142
286
  const html = document.documentElement;
@@ -146,35 +290,284 @@
146
290
  html.classList.remove(gameBoxNoScroll);
147
291
  };
148
292
  });
293
+
294
+ function getIsAppleMobile() {
295
+ return /iPhone|iPod/.test(navigator.userAgent);
296
+ }
297
+
298
+ function whatDevice() {
299
+ if (isAppleMobile) {
300
+ return 'IOS';
301
+ } else if (/Android/.test(navigator.userAgent)) {
302
+ return 'Android';
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Returns true if a device is a mobile phone (or similar)
308
+ */
309
+ function getIsMobile() {
310
+ // @ts-ignore
311
+ if (navigator?.userAgentData?.mobile !== undefined) {
312
+ // Supports for mobile flag
313
+ // @ts-ignore
314
+ return navigator.userAgentData.mobile;
315
+ } else if (isAppleMobile) {
316
+ return true;
317
+ } else if (/Android/.test(navigator.userAgent)) {
318
+ return true;
319
+ }
320
+
321
+ return false;
322
+ }
323
+
324
+ /**
325
+ * Returns true if the window is in full screen
326
+ * - Checks if CSS thinks we're in fullscreen mode
327
+ * - Checks if there is a fullscreen element (for safari)
328
+ */
329
+ function getIsFullscreen() {
330
+ if (
331
+ window.matchMedia(
332
+ '(display-mode: fullscreen) or (display-mode: standalone)'
333
+ ).matches
334
+ ) {
335
+ return true;
336
+ } else if (document.fullscreenElement) {
337
+ // Safari
338
+ return true;
339
+ }
340
+
341
+ return false;
342
+ }
343
+
344
+ async function requestFullscreen() {
345
+ console.debug('Request full screen');
346
+ show = false;
347
+
348
+ await document.documentElement.requestFullscreen();
349
+ isFullscreen = true;
350
+
351
+ setTimeout(() => {
352
+ show = true;
353
+ }, 1000);
354
+ }
355
+
356
+ // async function exitFullscreen() {
357
+ // console.debug('Exit full screen');
358
+ // show = false;
359
+
360
+ // await document.exitFullscreen();
361
+ // isFullscreen = false;
362
+
363
+ // setTimeout( () => { show = true; }, 1000 );
364
+ // }
365
+
366
+ $effect(() => {
367
+ // Update isFullscreen if window width or height changes
368
+
369
+ windowWidth;
370
+ windowHeight;
371
+
372
+ isFullscreen = getIsFullscreen();
373
+
374
+ // if( !isFullscreen )
375
+ // {
376
+ // show = false;
377
+ // setTimeout( () => { show = true; }, 1000 );
378
+ // }
379
+
380
+ // console.debug('isFullscreen', isFullscreen);
381
+ });
382
+
383
+ isDevMode = false;
384
+
385
+ function requestDevmode() {
386
+ isDevMode = true;
387
+ console.debug(isDevMode);
388
+ }
389
+
390
+ $effect(() => {
391
+ if (location.hostname === 'localhost') {
392
+ isDevMode = true;
393
+ }
394
+ });
395
+
396
+ $effect(() => {
397
+ if (isFullscreen) {
398
+ const url = new URL(window.location.href);
399
+ url.searchParams.set('preset', 'cinema');
400
+ window.history.pushState({}, '', url);
401
+ }
402
+ });
149
403
  </script>
150
404
 
151
405
  <svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
152
406
 
153
407
  {#if gameHeight}
154
- <div
155
- data-component="game-box"
156
- data-orientation={isLandscape ? 'landscape' : 'portrait'}
157
- bind:this={gameContainer}
158
- class="{base} {bg} {classes}"
159
- style:width="{gameWidth}px"
160
- style:height="{gameHeight}px"
161
- style:--game-width={gameWidth}
162
- style:--game-height={gameHeight}
163
- style:margin-left="{marginLeft}px"
164
- style:margin-right="{marginRight}px"
165
- style:margin-top="{marginTop}px"
166
- style:margin-bottom="{marginBottom}px"
167
- {style}
168
- >
169
- {#if isLandscape}
170
- {@render snippetLandscape()}
171
- {:else}
172
- {@render snippetPortrait()}
173
- {/if}
408
+ <div class:center>
409
+ <div
410
+ data-component="game-box"
411
+ data-orientation={isLandscape ? 'landscape' : 'portrait'}
412
+ bind:this={gameContainer}
413
+ class="{base} {bg} {classes}"
414
+ class:isMobile
415
+ style:width="{gameWidth}px"
416
+ style:height="{gameHeight}px"
417
+ style:--game-width={gameWidth}
418
+ style:--game-height={gameHeight}
419
+ style:margin-left="{marginLeft}px"
420
+ style:margin-right="{marginRight}px"
421
+ style:margin-top="{marginTop}px"
422
+ style:margin-bottom="{marginBottom}px"
423
+ {style}
424
+ >
425
+ {#if show}
426
+ {#if isLandscape}
427
+ <!-- Landscape -->
428
+ {#if snippetRequireFullscreen}
429
+ <!-- Require fullscreen -->
430
+ {#if isFullscreen && !isDevMode}
431
+ {@render snippetLandscape({
432
+ isMobile,
433
+ device,
434
+ isFullscreen,
435
+ isDevMode,
436
+ requestDevmode,
437
+ requestFullscreen,
438
+ gameWidth,
439
+ gameHeight
440
+ })}
441
+ {:else if supportsFullscreen && !isDevMode}
442
+ <!-- Require fullscreen (on landscape) -->
443
+ {@render snippetRequireFullscreen({
444
+ isMobile,
445
+ device,
446
+ isFullscreen,
447
+ isDevMode,
448
+ requestDevmode,
449
+ requestFullscreen,
450
+ gameWidth,
451
+ gameHeight
452
+ })}
453
+ {:else if isMobile && snippetInstallOnHomeScreen && !isDevMode}
454
+ <!-- Require install on home screen on mobile -->
455
+ {@render snippetInstallOnHomeScreen({
456
+ isMobile,
457
+ device,
458
+ isFullscreen,
459
+ isDevMode,
460
+ requestDevmode,
461
+ requestFullscreen,
462
+ gameWidth,
463
+ gameHeight
464
+ })}
465
+ {:else}
466
+ {@render snippetLandscape({
467
+ isMobile,
468
+ device,
469
+ isFullscreen,
470
+ isDevMode,
471
+ requestDevmode,
472
+ requestFullscreen,
473
+ gameWidth,
474
+ gameHeight
475
+ })}
476
+ {/if}
477
+ {:else}
478
+ <!-- Do not require fullscreen -->
479
+ <!-- *we do not try install home app -->
480
+ {@render snippetLandscape({
481
+ isMobile,
482
+ device,
483
+ isFullscreen,
484
+ isDevMode,
485
+ requestDevmode,
486
+ requestFullscreen,
487
+ gameWidth,
488
+ gameHeight
489
+ })}
490
+ {/if}
491
+ {:else}
492
+ <!-- Portrait -->
493
+ {#if snippetRequireFullscreen}
494
+ <!-- Require fullscreen -->
495
+ {#if isFullscreen && !isDevMode}
496
+ {@render snippetPortrait({
497
+ isMobile,
498
+ device,
499
+ isFullscreen,
500
+ isDevMode,
501
+ requestDevmode,
502
+ requestFullscreen,
503
+ gameWidth,
504
+ gameHeight
505
+ })}
506
+ {:else if supportsFullscreen && !isDevMode}
507
+ <!-- Require fullscreen (on landscape) -->
508
+ {@render snippetRequireFullscreen({
509
+ isMobile,
510
+ device,
511
+ isFullscreen,
512
+ isDevMode,
513
+ requestDevmode,
514
+ requestFullscreen,
515
+ gameWidth,
516
+ gameHeight
517
+ })}
518
+ {:else if isMobile && snippetInstallOnHomeScreen && !isDevMode}
519
+ <!-- Require install on home screen on mobile -->
520
+ {@render snippetInstallOnHomeScreen({
521
+ isMobile,
522
+ device,
523
+ isFullscreen,
524
+ isDevMode,
525
+ requestDevmode,
526
+ requestFullscreen,
527
+ gameWidth,
528
+ gameHeight
529
+ })}
530
+ {:else}
531
+ {@render snippetPortrait({
532
+ isMobile,
533
+ device,
534
+ isFullscreen,
535
+ isDevMode,
536
+ requestDevmode,
537
+ requestFullscreen,
538
+ gameWidth,
539
+ gameHeight
540
+ })}
541
+ {/if}
542
+ {:else}
543
+ <!-- Do not require fullscreen -->
544
+ <!-- *we do not try install home app -->
545
+ {@render snippetPortrait({
546
+ isMobile,
547
+ device,
548
+ isFullscreen,
549
+ isDevMode,
550
+ requestDevmode,
551
+ requestFullscreen,
552
+ gameWidth,
553
+ gameHeight
554
+ })}
555
+ {/if}
556
+ {/if}
557
+ {/if}
558
+ </div>
174
559
  </div>
175
560
  {/if}
176
561
 
177
562
  <style>
563
+ .center {
564
+ height: 100lvh;
565
+ display: grid;
566
+ justify-items: center;
567
+ align-items: center;
568
+ /* border: solid 1px red;*/
569
+ }
570
+
178
571
  :global(html.game-box-no-scroll) {
179
572
  overflow: clip;
180
573
  scrollbar-width: none; /* Firefox */
@@ -9,6 +9,11 @@ type GameBox = {
9
9
  style?: string;
10
10
  aspectOnLandscape?: number;
11
11
  aspectOnPortrait?: number;
12
+ marginLeft?: number;
13
+ marginRight?: number;
14
+ marginTop?: number;
15
+ marginBottom?: number;
16
+ center?: boolean;
12
17
  enableScaling?: boolean;
13
18
  designLandscape?: {
14
19
  width: number;
@@ -36,8 +41,10 @@ type GameBox = {
36
41
  max: number;
37
42
  };
38
43
  };
39
- snippetLandscape?: Snippet<[]>;
40
- snippetPortrait?: Snippet<[]>;
44
+ snippetLandscape?: Snippet<[SnippetParams]>;
45
+ snippetPortrait?: Snippet<[SnippetParams]>;
46
+ snippetRequireFullscreen?: Snippet<[SnippetParams]>;
47
+ snippetInstallOnHomeScreen?: Snippet<[SnippetParams]>;
41
48
  }>): void;
42
49
  };
43
50
  declare const GameBox: import("svelte").Component<{
@@ -48,6 +55,11 @@ declare const GameBox: import("svelte").Component<{
48
55
  style?: string;
49
56
  aspectOnLandscape?: number;
50
57
  aspectOnPortrait?: number;
58
+ marginLeft?: number;
59
+ marginRight?: number;
60
+ marginTop?: number;
61
+ marginBottom?: number;
62
+ center?: boolean;
51
63
  enableScaling?: boolean;
52
64
  designLandscape?: {
53
65
  width: number;
@@ -75,6 +87,54 @@ declare const GameBox: import("svelte").Component<{
75
87
  max: number;
76
88
  };
77
89
  };
78
- snippetLandscape?: import("svelte").Snippet;
79
- snippetPortrait?: import("svelte").Snippet;
90
+ snippetLandscape?: import("svelte").Snippet<[{
91
+ isMobile: boolean;
92
+ device: string;
93
+ isFullscreen: boolean;
94
+ isDevMode: boolean;
95
+ requestDevmode: Function;
96
+ requestFullscreen: Function;
97
+ gameWidth: number;
98
+ gameHeight: number;
99
+ }]>;
100
+ snippetPortrait?: import("svelte").Snippet<[{
101
+ isMobile: boolean;
102
+ device: string;
103
+ isFullscreen: boolean;
104
+ isDevMode: boolean;
105
+ requestDevmode: Function;
106
+ requestFullscreen: Function;
107
+ gameWidth: number;
108
+ gameHeight: number;
109
+ }]>;
110
+ snippetRequireFullscreen?: import("svelte").Snippet<[{
111
+ isMobile: boolean;
112
+ device: string;
113
+ isFullscreen: boolean;
114
+ isDevMode: boolean;
115
+ requestDevmode: Function;
116
+ requestFullscreen: Function;
117
+ gameWidth: number;
118
+ gameHeight: number;
119
+ }]>;
120
+ snippetInstallOnHomeScreen?: import("svelte").Snippet<[{
121
+ isMobile: boolean;
122
+ device: string;
123
+ isFullscreen: boolean;
124
+ isDevMode: boolean;
125
+ requestDevmode: Function;
126
+ requestFullscreen: Function;
127
+ gameWidth: number;
128
+ gameHeight: number;
129
+ }]>;
80
130
  }, {}, "">;
131
+ type SnippetParams = {
132
+ isMobile: boolean;
133
+ device: string;
134
+ isFullscreen: boolean;
135
+ isDevMode: boolean;
136
+ requestDevmode: Function;
137
+ requestFullscreen: Function;
138
+ gameWidth: number;
139
+ gameHeight: number;
140
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-sveltekit",
3
- "version": "0.1.61",
3
+ "version": "0.1.64",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"