@klodd/ds 3.0.1 → 3.0.3

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/css/base/pwa.css CHANGED
@@ -36,6 +36,14 @@ body {
36
36
  overscroll-behavior: contain; /* stoppar pull-to-refresh-konflikter */
37
37
  }
38
38
 
39
+ /* HTML5 hidden-attribut respekteras alltid - oavsett vilken display-property
40
+ en komponent satter sig sjalv. Default UA-stylesheet har display:none for
41
+ [hidden] men en mer specifik komponentregel (t.ex. .my-form { display: flex })
42
+ kan over-rida det. !important saker att hidden alltid vinner. */
43
+ [hidden] {
44
+ display: none !important;
45
+ }
46
+
39
47
  /* Globala fokusringar - alltid synliga vid keyboard-navigation, aldrig vid tap. */
40
48
  :focus-visible {
41
49
  outline: 2px solid var(--border-focus);
@@ -1,132 +1,165 @@
1
1
  /*--------------------------------------------------------------
2
- Ekonom PWA register
3
- - isNetworkError() - iOS-saker nat-detektering
4
- - Service worker registrering med updateViaCache: 'none'
5
- - Install-prompt (beforeinstallprompt for Chrome/Android)
6
- - iOS install-hint (Safari har ingen beforeinstallprompt)
7
- --------------------------------------------------------------*/
2
+ @klodd/ds - PWA-registrering + install-prompts
8
3
 
9
- /*--------------------------------------------------------------
10
- Natverksfel-detektering
11
- navigator.onLine ljuger pa iOS Safari. Triangulera genom att ocksa
12
- kolla errortyp och message-text. Returnerar true om ett fetch-fel
13
- troligen beror pa natverket (= OK att visa offline-meddelande).
14
- --------------------------------------------------------------*/
15
- window.isNetworkError = function ( err ) {
16
- if ( ! navigator.onLine ) return true;
17
- if ( err instanceof TypeError ) return true;
18
- if ( err && err.message ) {
19
- return /network|failed to fetch|load failed|offline|cancelled/i.test( err.message );
20
- }
21
- return false;
22
- };
4
+ Auto-detekterar app-namn fran <html data-app="X">-attributet.
5
+ Defaults: appName = capitalize(data-app), appKey = data-app, swPath = "/sw.js".
6
+ Funkar identiskt for Jubb och Ekonom utan konfiguration.
23
7
 
24
- /*--------------------------------------------------------------
25
- SW-registrering
26
- --------------------------------------------------------------*/
27
- if ( 'serviceWorker' in navigator ) {
28
- window.addEventListener( 'load', async function () {
29
- try {
30
- const reg = await navigator.serviceWorker.register( '/sw.js', {
31
- scope: '/',
32
- updateViaCache: 'none',
33
- } );
34
- console.log( 'Ekonom SW registered. Scope:', reg.scope );
35
- } catch ( err ) {
36
- console.warn( 'Ekonom SW registration failed:', err );
37
- }
38
- } );
39
- }
8
+ Manuell override: KloddPWA.init({ appName, appKey, swPath }) innan load.
40
9
 
41
- /*--------------------------------------------------------------
42
- Install-prompt (Android / Chrome / Edge)
43
- --------------------------------------------------------------*/
44
- let deferredInstallPrompt = null;
45
-
46
- window.addEventListener( 'beforeinstallprompt', function ( e ) {
47
- e.preventDefault();
48
- deferredInstallPrompt = e;
49
- maybeShowInstallChip();
50
- } );
51
-
52
- function maybeShowInstallChip() {
53
- // Inte om redan dismissat denna session
54
- if ( sessionStorage.getItem( 'ekonom_install_dismissed' ) ) return;
55
- // Inte om redan installerad
56
- if ( window.matchMedia( '(display-mode: standalone)' ).matches ) return;
57
-
58
- // Bara efter 3+ besok
59
- const visits = parseInt( localStorage.getItem( 'ekonom_visits' ) || '0', 10 ) + 1;
60
- localStorage.setItem( 'ekonom_visits', String( visits ) );
61
- if ( visits < 3 ) return;
62
-
63
- showInstallChip();
64
- }
65
-
66
- function showInstallChip() {
67
- if ( document.getElementById( 'ekonom-install-chip' ) ) return;
68
-
69
- const chip = document.createElement( 'div' );
70
- chip.className = 'install-chip';
71
- chip.id = 'ekonom-install-chip';
72
- chip.setAttribute( 'role', 'dialog' );
73
- chip.setAttribute( 'aria-label', 'Installera Ekonom' );
74
- chip.innerHTML =
75
- '<span>Installera Ekonom p\u00e5 hemsk\u00e4rmen</span>' +
76
- '<button type="button" data-install>Installera</button>' +
77
- '<button type="button" data-dismiss aria-label="St\u00e4ng">\u00D7</button>';
78
- document.body.appendChild( chip );
79
-
80
- chip.querySelector( '[data-install]' ).addEventListener( 'click', async function () {
81
- if ( ! deferredInstallPrompt ) { chip.remove(); return; }
82
- deferredInstallPrompt.prompt();
83
- try { await deferredInstallPrompt.userChoice; } catch ( e ) {}
84
- deferredInstallPrompt = null;
85
- chip.remove();
86
- } );
87
- chip.querySelector( '[data-dismiss]' ).addEventListener( 'click', function () {
88
- sessionStorage.setItem( 'ekonom_install_dismissed', '1' );
89
- chip.remove();
90
- } );
91
- }
92
-
93
- /*--------------------------------------------------------------
94
- iOS install-hint
95
- Safari har ingen beforeinstallprompt - anvandaren maste manuellt
96
- "Dela" -> "Lagg till pa hemskarmen". Visa en diskret chip efter
97
- 3+ besok sa de vet var de ska titta.
10
+ Funktioner:
11
+ - isNetworkError() - iOS-saker nat-detektering (window.isNetworkError)
12
+ - SW-registrering med updateViaCache: 'none'
13
+ - Install-prompt (beforeinstallprompt for Chrome/Android)
14
+ - iOS install-hint (Safari har ingen beforeinstallprompt)
98
15
  --------------------------------------------------------------*/
99
- window.addEventListener( 'load', function () {
100
- const isIOS = /iPad|iPhone|iPod/.test( navigator.userAgent ) && ! window.MSStream;
101
- const inStandalone = window.matchMedia( '(display-mode: standalone)' ).matches
102
- || window.navigator.standalone;
103
-
104
- if ( ! isIOS || inStandalone ) return;
105
-
106
- if ( sessionStorage.getItem( 'ekonom_ios_install_dismissed' ) ) return;
107
-
108
- // Anvand samma besok-rakning som Android-chip:en.
109
- const visits = parseInt( localStorage.getItem( 'ekonom_visits' ) || '0', 10 );
110
- if ( visits < 3 ) return;
111
-
112
- showIOSInstallHint();
113
- } );
114
-
115
- function showIOSInstallHint() {
116
- if ( document.getElementById( 'ekonom-ios-install-hint' ) ) return;
117
-
118
- const hint = document.createElement( 'div' );
119
- hint.className = 'install-chip install-chip-ios';
120
- hint.id = 'ekonom-ios-install-hint';
121
- hint.setAttribute( 'role', 'dialog' );
122
- hint.setAttribute( 'aria-label', 'Installera Ekonom pa iOS' );
123
- hint.innerHTML =
124
- '<span>Tryck <strong>Dela</strong> \u2192 <strong>L\u00e4gg till p\u00e5 hemsk\u00e4rmen</strong></span>' +
125
- '<button type="button" data-dismiss aria-label="St\u00e4ng">\u00D7</button>';
126
- document.body.appendChild( hint );
127
-
128
- hint.querySelector( '[data-dismiss]' ).addEventListener( 'click', function () {
129
- sessionStorage.setItem( 'ekonom_ios_install_dismissed', '1' );
130
- hint.remove();
131
- } );
132
- }
16
+ (function (root, doc) {
17
+ 'use strict';
18
+
19
+ /*------------------------------------------------------------
20
+ Auto-detect: las data-app fran <html>. Override via KloddPWA._cfg.
21
+ ------------------------------------------------------------*/
22
+ function getCfg() {
23
+ const userCfg = root.KloddPWA && root.KloddPWA._cfg ? root.KloddPWA._cfg : {};
24
+ const dataApp = (doc.documentElement.getAttribute('data-app') || 'app').toLowerCase();
25
+ const cap = dataApp.charAt(0).toUpperCase() + dataApp.slice(1);
26
+ return {
27
+ appName: userCfg.appName || cap,
28
+ appKey: userCfg.appKey || dataApp,
29
+ swPath: userCfg.swPath || '/sw.js',
30
+ minVisits: userCfg.minVisits || 3,
31
+ };
32
+ }
33
+
34
+ /*------------------------------------------------------------
35
+ Network-error-detektor (window-prop sa appens egen JS kan anvanda).
36
+ navigator.onLine ljuger pa iOS Safari - triangulera mot errortyp.
37
+ ------------------------------------------------------------*/
38
+ root.isNetworkError = function (err) {
39
+ if (!navigator.onLine) return true;
40
+ if (err instanceof TypeError) return true;
41
+ if (err && err.message) {
42
+ return /network|failed to fetch|load failed|offline|cancelled/i.test(err.message);
43
+ }
44
+ return false;
45
+ };
46
+
47
+ /*------------------------------------------------------------
48
+ SW-registrering
49
+ ------------------------------------------------------------*/
50
+ function registerSW(cfg) {
51
+ if (!('serviceWorker' in navigator)) return;
52
+ root.addEventListener('load', async function () {
53
+ try {
54
+ const reg = await navigator.serviceWorker.register(cfg.swPath, {
55
+ scope: '/',
56
+ updateViaCache: 'none',
57
+ });
58
+ console.log(cfg.appName + ' SW registered. Scope:', reg.scope);
59
+ } catch (err) {
60
+ console.warn(cfg.appName + ' SW registration failed:', err);
61
+ }
62
+ });
63
+ }
64
+
65
+ /*------------------------------------------------------------
66
+ Install-prompt (Android / Chrome / Edge)
67
+ ------------------------------------------------------------*/
68
+ let deferredInstallPrompt = null;
69
+
70
+ function setupInstallPrompt(cfg) {
71
+ root.addEventListener('beforeinstallprompt', function (e) {
72
+ e.preventDefault();
73
+ deferredInstallPrompt = e;
74
+ maybeShowInstallChip(cfg);
75
+ });
76
+ }
77
+
78
+ function maybeShowInstallChip(cfg) {
79
+ if (sessionStorage.getItem(cfg.appKey + '_install_dismissed')) return;
80
+ if (root.matchMedia('(display-mode: standalone)').matches) return;
81
+ const visits = parseInt(localStorage.getItem(cfg.appKey + '_visits') || '0', 10) + 1;
82
+ localStorage.setItem(cfg.appKey + '_visits', String(visits));
83
+ if (visits < cfg.minVisits) return;
84
+ showInstallChip(cfg);
85
+ }
86
+
87
+ function showInstallChip(cfg) {
88
+ const id = cfg.appKey + '-install-chip';
89
+ if (doc.getElementById(id)) return;
90
+
91
+ const chip = doc.createElement('div');
92
+ chip.className = 'install-chip';
93
+ chip.id = id;
94
+ chip.setAttribute('role', 'dialog');
95
+ chip.setAttribute('aria-label', 'Installera ' + cfg.appName);
96
+ chip.innerHTML =
97
+ '<span>Installera ' + cfg.appName + ' på hemskärmen</span>' +
98
+ '<button type="button" data-install>Installera</button>' +
99
+ '<button type="button" data-dismiss aria-label="Stäng">×</button>';
100
+ doc.body.appendChild(chip);
101
+
102
+ chip.querySelector('[data-install]').addEventListener('click', async function () {
103
+ if (!deferredInstallPrompt) { chip.remove(); return; }
104
+ deferredInstallPrompt.prompt();
105
+ try { await deferredInstallPrompt.userChoice; } catch (e) {}
106
+ deferredInstallPrompt = null;
107
+ chip.remove();
108
+ });
109
+ chip.querySelector('[data-dismiss]').addEventListener('click', function () {
110
+ sessionStorage.setItem(cfg.appKey + '_install_dismissed', '1');
111
+ chip.remove();
112
+ });
113
+ }
114
+
115
+ /*------------------------------------------------------------
116
+ iOS install-hint
117
+ Safari har ingen beforeinstallprompt - manuell flow via "Dela" ->
118
+ "Lagg till pa hemskarmen". Visa diskret hint efter N besok.
119
+ ------------------------------------------------------------*/
120
+ function setupIOSHint(cfg) {
121
+ root.addEventListener('load', function () {
122
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !root.MSStream;
123
+ const inStandalone = root.matchMedia('(display-mode: standalone)').matches
124
+ || root.navigator.standalone;
125
+ if (!isIOS || inStandalone) return;
126
+ if (sessionStorage.getItem(cfg.appKey + '_ios_install_dismissed')) return;
127
+ const visits = parseInt(localStorage.getItem(cfg.appKey + '_visits') || '0', 10);
128
+ if (visits < cfg.minVisits) return;
129
+ showIOSInstallHint(cfg);
130
+ });
131
+ }
132
+
133
+ function showIOSInstallHint(cfg) {
134
+ const id = cfg.appKey + '-ios-install-hint';
135
+ if (doc.getElementById(id)) return;
136
+
137
+ const hint = doc.createElement('div');
138
+ hint.className = 'install-chip install-chip-ios';
139
+ hint.id = id;
140
+ hint.setAttribute('role', 'dialog');
141
+ hint.setAttribute('aria-label', 'Installera ' + cfg.appName + ' pa iOS');
142
+ hint.innerHTML =
143
+ '<span>Tryck <strong>Dela</strong> → <strong>Lägg till på hemskärmen</strong></span>' +
144
+ '<button type="button" data-dismiss aria-label="Stäng">×</button>';
145
+ doc.body.appendChild(hint);
146
+
147
+ hint.querySelector('[data-dismiss]').addEventListener('click', function () {
148
+ sessionStorage.setItem(cfg.appKey + '_ios_install_dismissed', '1');
149
+ hint.remove();
150
+ });
151
+ }
152
+
153
+ /*------------------------------------------------------------
154
+ Public API. Anvand KloddPWA.init({...}) FORE script-laddning av
155
+ denna fil (sa _cfg ar satt nar getCfg() koras), eller bara lat
156
+ auto-detect kora pa <html data-app="X">.
157
+ ------------------------------------------------------------*/
158
+ const cfg = getCfg();
159
+ root.KloddPWA = root.KloddPWA || {};
160
+ root.KloddPWA.config = cfg;
161
+
162
+ registerSW(cfg);
163
+ setupInstallPrompt(cfg);
164
+ setupIOSHint(cfg);
165
+ })(typeof window !== 'undefined' ? window : globalThis, document);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Klodd Design System - shared tokens, typography, components and JS for Jubb, Ekonom, and future apps. v2.0 inkluderar all komponentkod och delad JS - app-repona haller bara data och affarslogik.",
5
5
  "main": "css/index.css",
6
6
  "files": [