@fristil/designsystem 0.4.0 → 0.5.0

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 (116) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +22 -7
  3. package/dist/components/frittstaende/connection-status/connection-status.d.ts +28 -0
  4. package/dist/components/frittstaende/connection-status/connection-status.d.ts.map +1 -0
  5. package/dist/components/frittstaende/connection-status/connection-status.js +22 -0
  6. package/dist/components/frittstaende/connection-status/connection-status.js.map +1 -0
  7. package/dist/components/frittstaende/connection-status/fs-connection-status.d.ts +54 -0
  8. package/dist/components/frittstaende/connection-status/fs-connection-status.d.ts.map +1 -0
  9. package/dist/components/frittstaende/connection-status/fs-connection-status.js +123 -0
  10. package/dist/components/frittstaende/connection-status/fs-connection-status.js.map +1 -0
  11. package/dist/components/frittstaende/session-timeout/fs-session-timeout.d.ts +56 -0
  12. package/dist/components/frittstaende/session-timeout/fs-session-timeout.d.ts.map +1 -0
  13. package/dist/components/frittstaende/session-timeout/fs-session-timeout.js +179 -0
  14. package/dist/components/frittstaende/session-timeout/fs-session-timeout.js.map +1 -0
  15. package/dist/components/frittstaende/session-timeout/session-timeout.d.ts +28 -0
  16. package/dist/components/frittstaende/session-timeout/session-timeout.d.ts.map +1 -0
  17. package/dist/components/frittstaende/session-timeout/session-timeout.js +22 -0
  18. package/dist/components/frittstaende/session-timeout/session-timeout.js.map +1 -0
  19. package/dist/components/frittstaende/toast/fs-toast.d.ts +16 -22
  20. package/dist/components/frittstaende/toast/fs-toast.d.ts.map +1 -1
  21. package/dist/components/frittstaende/toast/fs-toast.js +38 -27
  22. package/dist/components/frittstaende/toast/fs-toast.js.map +1 -1
  23. package/dist/components/frittstaende/toast/toast.d.ts +34 -0
  24. package/dist/components/frittstaende/toast/toast.d.ts.map +1 -0
  25. package/dist/components/frittstaende/toast/toast.js +26 -0
  26. package/dist/components/frittstaende/toast/toast.js.map +1 -0
  27. package/dist/components/ramme/error-summary/error-summary.d.ts +39 -0
  28. package/dist/components/ramme/error-summary/error-summary.d.ts.map +1 -0
  29. package/dist/components/ramme/error-summary/error-summary.js +31 -0
  30. package/dist/components/ramme/error-summary/error-summary.js.map +1 -0
  31. package/dist/components/ramme/error-summary/fs-error-summary.d.ts +16 -42
  32. package/dist/components/ramme/error-summary/fs-error-summary.d.ts.map +1 -1
  33. package/dist/components/ramme/error-summary/fs-error-summary.js +57 -92
  34. package/dist/components/ramme/error-summary/fs-error-summary.js.map +1 -1
  35. package/dist/components/ramme/field/field-core.d.ts +31 -0
  36. package/dist/components/ramme/field/field-core.d.ts.map +1 -1
  37. package/dist/components/ramme/field/field-core.js +31 -0
  38. package/dist/components/ramme/field/field-core.js.map +1 -1
  39. package/dist/components/ramme/field/fs-field.d.ts +38 -40
  40. package/dist/components/ramme/field/fs-field.d.ts.map +1 -1
  41. package/dist/components/ramme/field/fs-field.js +127 -54
  42. package/dist/components/ramme/field/fs-field.js.map +1 -1
  43. package/dist/components/ramme/popover/fs-popover.d.ts +20 -27
  44. package/dist/components/ramme/popover/fs-popover.d.ts.map +1 -1
  45. package/dist/components/ramme/popover/fs-popover.js +68 -54
  46. package/dist/components/ramme/popover/fs-popover.js.map +1 -1
  47. package/dist/components/ramme/popover/popover.d.ts +43 -0
  48. package/dist/components/ramme/popover/popover.d.ts.map +1 -0
  49. package/dist/components/ramme/popover/popover.js +49 -0
  50. package/dist/components/ramme/popover/popover.js.map +1 -0
  51. package/dist/components/ramme/suggestion/fs-suggestion.d.ts +75 -0
  52. package/dist/components/ramme/suggestion/fs-suggestion.d.ts.map +1 -0
  53. package/dist/components/ramme/suggestion/fs-suggestion.js +232 -0
  54. package/dist/components/ramme/suggestion/fs-suggestion.js.map +1 -0
  55. package/dist/components/ramme/suggestion/suggestion.d.ts +97 -0
  56. package/dist/components/ramme/suggestion/suggestion.d.ts.map +1 -0
  57. package/dist/components/ramme/suggestion/suggestion.js +79 -0
  58. package/dist/components/ramme/suggestion/suggestion.js.map +1 -0
  59. package/dist/components/ramme/tabs/fs-tabs.d.ts +31 -36
  60. package/dist/components/ramme/tabs/fs-tabs.d.ts.map +1 -1
  61. package/dist/components/ramme/tabs/fs-tabs.js +62 -84
  62. package/dist/components/ramme/tabs/fs-tabs.js.map +1 -1
  63. package/dist/components/ramme/tabs/tabs.d.ts +50 -0
  64. package/dist/components/ramme/tabs/tabs.d.ts.map +1 -0
  65. package/dist/components/ramme/tabs/tabs.js +54 -0
  66. package/dist/components/ramme/tabs/tabs.js.map +1 -0
  67. package/dist/fs.d.ts +94 -0
  68. package/dist/fs.d.ts.map +1 -1
  69. package/dist/fs.js +42 -0
  70. package/dist/fs.js.map +1 -1
  71. package/dist/index.d.ts +10 -3
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +10 -3
  74. package/dist/index.js.map +1 -1
  75. package/dist/jsx/react.d.ts +35 -50
  76. package/dist/jsx/react.d.ts.map +1 -1
  77. package/dist/react.d.ts +142 -1
  78. package/dist/react.d.ts.map +1 -1
  79. package/dist/react.js +71 -2
  80. package/dist/react.js.map +1 -1
  81. package/package.json +15 -18
  82. package/src/components/frittstaende/connection-status/connection-status.css +33 -0
  83. package/src/components/frittstaende/connection-status/connection-status.ts +41 -0
  84. package/src/components/frittstaende/connection-status/fs-connection-status.ts +144 -0
  85. package/src/components/frittstaende/session-timeout/fs-session-timeout.ts +212 -0
  86. package/src/components/frittstaende/session-timeout/session-timeout.css +43 -0
  87. package/src/components/frittstaende/session-timeout/session-timeout.ts +42 -0
  88. package/src/components/frittstaende/toast/fs-toast.ts +39 -30
  89. package/src/components/frittstaende/toast/toast.ts +45 -0
  90. package/src/components/ramme/error-summary/error-summary.ts +55 -0
  91. package/src/components/ramme/error-summary/fs-error-summary.ts +62 -119
  92. package/src/components/ramme/field/field-core.ts +32 -0
  93. package/src/components/ramme/field/fs-field.ts +144 -61
  94. package/src/components/ramme/popover/fs-popover.ts +78 -72
  95. package/src/components/ramme/popover/popover.ts +79 -0
  96. package/src/components/ramme/suggestion/fs-suggestion.ts +265 -0
  97. package/src/components/ramme/suggestion/suggestion.ts +105 -0
  98. package/src/components/ramme/tabs/fs-tabs.ts +67 -98
  99. package/src/components/ramme/tabs/tabs.ts +104 -0
  100. package/dist/components/frittstaende/calendar/fs-calendar.d.ts +0 -52
  101. package/dist/components/frittstaende/calendar/fs-calendar.d.ts.map +0 -1
  102. package/dist/components/frittstaende/calendar/fs-calendar.js +0 -462
  103. package/dist/components/frittstaende/calendar/fs-calendar.js.map +0 -1
  104. package/dist/components/frittstaende/date-field/fs-date-field.d.ts +0 -110
  105. package/dist/components/frittstaende/date-field/fs-date-field.d.ts.map +0 -1
  106. package/dist/components/frittstaende/date-field/fs-date-field.js +0 -264
  107. package/dist/components/frittstaende/date-field/fs-date-field.js.map +0 -1
  108. package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts +0 -133
  109. package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts.map +0 -1
  110. package/dist/components/frittstaende/suggestion/fs-suggestion.js +0 -280
  111. package/dist/components/frittstaende/suggestion/fs-suggestion.js.map +0 -1
  112. package/src/components/frittstaende/calendar/fs-calendar.ts +0 -525
  113. package/src/components/frittstaende/date-field/date-field.css +0 -54
  114. package/src/components/frittstaende/date-field/fs-date-field.ts +0 -309
  115. package/src/components/frittstaende/suggestion/fs-suggestion.ts +0 -333
  116. /package/src/components/{frittstaende → ramme}/suggestion/suggestion.css +0 -0
package/CHANGELOG.md CHANGED
@@ -16,6 +16,118 @@ kommer i et nytt undertall.
16
16
 
17
17
  ## Ikke utgitt
18
18
 
19
+ ## 0.5.0 (2026-09-21)
20
+
21
+ ### Brytende
22
+
23
+ - **Serveren skriver markupen, komponentene fester bare oppførsel.** Seks
24
+ komponenter satte tidligere klasser, roller og tilgjengelighetskoblinger på
25
+ elementer serveren hadde sendt. Det virker ikke i en app med server-rendret
26
+ HTML: Datastars morfing fjerner hvert attributt som ikke står i HTML-en
27
+ serveren nettopp sendte, og React kan kaste bort noder et egendefinert
28
+ element har lagt inn i et tre React eier. Testet med ekte Datastar mistet et
29
+ `<fs-field>` både `aria-describedby`, klassen på ledeteksten og den skjulte
30
+ feilmeldingen etter én patch, og fikk dem ikke tilbake.
31
+
32
+ Markupen skrives nå av nye byggere i `fs`: `fs.errorSummary()`,
33
+ `fs.popover()`, `fs.tabs()`, `fs.suggestion()` og `fs.toast()`. Byggerne
34
+ setter også `data-preserve-attr` for de attributtene komponenten endrer
35
+ underveis, så du slipper å vite om det. Se «Server først» i dokumentasjonen.
36
+
37
+ - **`<fs-calendar>` og `<fs-date-field>` er fjernet.** Nettleserens eget panel
38
+ i `<input type="date">` gjør jobben, og `fs.field()` sammen med
39
+ `fs.input({ type: "date" })` gir et komplett datofelt. Mønstersiden «Dato i
40
+ et skjema» viser oppsettet, med de to forskjellene vi har testet mellom nettleserne.
41
+ `exports`-oppføringene `./calendar`, `./date-field` og `./date-field.css` er
42
+ borte, og det samme er `--fs-calendar-*`- og `--fs-date-field-*`-variablene
43
+ og alle `part`-navnene i kalenderen.
44
+
45
+ - **`<fs-suggestion>` er flyttet fra `frittstående` til `ramme`.** Den lagde
46
+ tidligere hele feltet selv, så det fantes verken ledetekst eller
47
+ inndatafelt før skriptet hadde kjørt, og ingenting ble med i innsendingen.
48
+ Nå skriver serveren feltet og lista med `fs.suggestion()`.
49
+
50
+ - **`<fs-tabs>` har ingen `selected`- eller `label`-attributter lenger.**
51
+ Hvilken fane som er valgt står i markupen serveren sendte. `<fs-error-summary>`
52
+ har ikke lenger `heading`: overskriften skrives av serveren.
53
+
54
+ - **Ingen komponent bruker shadow DOM.** `::part()` er dermed ikke lenger en
55
+ del av det offentlige API-et.
56
+
57
+ - **`<fs-field>` leser tilstanden fra markupen, ikke fra egne attributter.**
58
+ Skrev serveren feltet med `fs.field()`, står `aria-invalid` og
59
+ `data-required` allerede på elementene. Komponenten regnet tidligere ut sitt
60
+ eget svar fra attributtene på verten, fant ingenting der, og fjernet det
61
+ serveren nettopp hadde skrevet. De to halvdelene av API-et kranglet altså
62
+ med hverandre når de ble brukt sammen. Den setter heller ikke lenger
63
+ `aria-hidden` på feilmeldingen: `hidden` tar den allerede ut av
64
+ tilgjengelighetstreet, og attributtet ga hydreringsfeil i React.
65
+
66
+ ### Lagt til
67
+
68
+ - **`<fs-session-timeout>`** varsler før en innlogget økt går ut, med en
69
+ nedtelling som leses opp ved terskler i stedet for hvert sekund, og fokus
70
+ som går tilbake dit brukeren var.
71
+
72
+ - **`<fs-connection-status>`** sier fra når forbindelsen til serveren er
73
+ borte. `navigator.onLine` sier bare at maskinen har et nettverk, så
74
+ `reportFailure()` og `reportSuccess()` lar appen melde fra selv.
75
+
76
+ - **Siden «Server først»** forklarer hvorfor markupen kommer fra serveren, med
77
+ testene bak, og hva det betyr i TanStack Start, React Server Components og
78
+ Datastar.
79
+
80
+ - **Mønstersiden «Dato i et skjema»** viser hvordan du ber om en dato uten en
81
+ datovelger fra oss, med de to forskjellene vi har testet mellom nettleserne:
82
+ WebKit krever skilletegnene, og Chromium tar imot feil rekkefølge som
83
+ gyldig.
84
+
85
+ - **`FIELD_PRESERVED_ATTRIBUTES`** er lista serveren skal skrive som
86
+ `data-preserve-attr` når den ikke kan kalle `fs.field()` selv.
87
+
88
+ ### Rettet
89
+
90
+ - **`<fs-toast duration="0">` lot ikke meldingene bli stående.** Attributtet
91
+ ble lest som «ugyldig» og falt tilbake til seks sekunder, så bare
92
+ `show(…, { duration: 0 })` virket.
93
+
94
+ - **`<fs-error-summary>` fikk ikke fokus når serveren bare tok bort `hidden`.**
95
+ Komponenten så etter endringer i barna, ikke i attributtene, så en
96
+ oppsummering som nettopp ble synlig ble stående uten fokus.
97
+
98
+ - **`<fs-error-summary>` finner nå feltet i sin egen rot.** Oppslaget gikk mot
99
+ `document`, som ikke ser inn i en skyggerot, så lenken ble en vanlig
100
+ ankerlenke uten fokusflytting når skjemaet lå inne i en annen komponent.
101
+
102
+ - **`FIELD_PRESERVED_ATTRIBUTES` dekker alle tre elementene.** Den dekket bare
103
+ kontrollen, men komponenten legger også `fs-label` og markeringene på
104
+ ledeteksten og skjuler feilmeldingen. Serveren må derfor skrive én liste per
105
+ element, og konstanten har nå `label`, `control` og `error`.
106
+
107
+ - **`<fs-popover>` lot seg ikke åpne etter at den var flyttet i DOM-en.**
108
+ Referansen til knappen ble stående, så lytteren ble aldri festet på nytt.
109
+
110
+ - **`fs.popover({ open: true })` sier det nå på verten.** Før satte den bare
111
+ `aria-expanded` på knappen, så markupen meldte at panelet var åpent mens
112
+ komponenten mente det var lukket.
113
+
114
+ - **`<fs-suggestion>` mistet musevalg etter at serveren sendte en ny liste.**
115
+ Alternativene fikk bare lytteren sin når selve feltet var nytt.
116
+
117
+ ### Endret
118
+
119
+ - **React-inngangen døper også om `tabindex` til `tabIndex`,** og dekker de nye
120
+ sammensatte byggerne. `fs.errorSummary()`, `fs.popover()`, `fs.tabs()`,
121
+ `fs.suggestion()` og `fs.toast()` finnes nå også i
122
+ `@fristil/designsystem/react`, der hvert attributtsett er døpt om for seg.
123
+ Pakken har fortsatt ingen avhengighet til React: dette er ren omdøping av
124
+ tre objektnøkler.
125
+
126
+ - **`lit` er ikke lenger en avhengighet.** Etter omleggingen rendrer ingen
127
+ komponent sitt eget innhold, så Lit ble brukt til nesten ingenting.
128
+ Komponentene er vanlige `HTMLElement`-klasser, og pakken har nå ingen
129
+ avhengigheter i det hele tatt.
130
+
19
131
  ## 0.4.0 (2026-09-21)
20
132
 
21
133
  ### Endret
package/README.md CHANGED
@@ -12,9 +12,7 @@ Hele dokumentasjonen, med komponentsider, tokens, mønstre og eksempler for fire
12
12
  npm install @fristil/designsystem
13
13
  ```
14
14
 
15
- Pakken har én avhengighet, `lit`, og den installeres sammen med pakken. Komponentene som er web components er bygd den.
16
-
17
- Den koster rundt 9,8 kB gzippet, og bare for den som registrerer minst én web component. Bruker du bare CSS-komponentene, havner Lit aldri i bunten din: ingenting registreres ved import alene, så den kommer først med når du kaller en `defineFs*`.
15
+ Pakken har ingen avhengigheter. Web-komponentene er vanlige `HTMLElement`-klasser, og ingenting registreres ved import alene, du betaler bare for det du faktisk kaller.
18
16
 
19
17
  ## CSS-komponenter
20
18
 
@@ -54,17 +52,34 @@ import { fs } from "@fristil/designsystem/react"
54
52
  Ingenting registreres ved import alene. Du kaller `defineFs*` selv, så du bestemmer når elementet finnes og hva det skal hete:
55
53
 
56
54
  ```js
57
- import { defineFsDateField } from "@fristil/designsystem/date-field"
55
+ import { defineFsSessionTimeout } from "@fristil/designsystem/session-timeout"
58
56
  import "@fristil/designsystem/tokens.css"
59
- import "@fristil/designsystem/date-field.css"
57
+ import "@fristil/designsystem/session-timeout.css"
60
58
 
61
- defineFsDateField()
59
+ defineFsSessionTimeout()
62
60
  ```
63
61
 
64
62
  ```html
65
- <fs-date-field label="Fødselsdato" value="2026-05-31"></fs-date-field>
63
+ <fs-session-timeout class="fs-session-timeout" warn-at="1500" expires-at="1800"
64
+ data-ignore-morph></fs-session-timeout>
65
+ ```
66
+
67
+ ## Serveren skriver markupen
68
+
69
+ Fristil er bygget for apper der HTML-en kommer fra serveren, enten det er TanStack Start, React Server Components eller Datastar. Ingen komponent rendrer sitt eget innhold i DOM serveren eier, fordi rammeverket rundt da river det bort igjen ved neste oppdatering.
70
+
71
+ Byggerne i `fs` gir markupen, komponentene gir oppførselen, og de to overlapper ikke:
72
+
73
+ ```js
74
+ import { fs } from "@fristil/designsystem"
75
+
76
+ fs.errorSummary({ count: 2, id: "feil" })
77
+ // { container: { class: "fs-error-summary", role: "alert", tabindex: "-1", id: "feil" },
78
+ // title: { class: "fs-error-summary__title" } }
66
79
  ```
67
80
 
81
+ Hele begrunnelsen, med testene bak, står på [Server først](https://fristil.netlify.app/server-forst/).
82
+
68
83
  ## Eget fargetema
69
84
 
70
85
  Kommandolinja lager et fullt tema av merkefargene dine, med kontrastkravene regnet ut i OKLCH:
@@ -0,0 +1,28 @@
1
+ export declare const CONNECTION_STATUS_CLASS: "fs-connection-status";
2
+ export declare const CONNECTION_STATUS_BAR_CLASS: "fs-connection-status__bar";
3
+ export type ConnectionStatusOptions = {
4
+ /** Teksten når forbindelsen er borte. */
5
+ offlineText?: string;
6
+ /** Teksten når den kommer tilbake. Vises en kort stund. */
7
+ onlineText?: string;
8
+ };
9
+ export type ConnectionStatusAttributes = {
10
+ class: typeof CONNECTION_STATUS_CLASS;
11
+ "offline-text"?: string;
12
+ "online-text"?: string;
13
+ "data-ignore-morph": "";
14
+ };
15
+ /**
16
+ * Attributtene for linja som sier fra at forbindelsen er borte.
17
+ *
18
+ * Serveren kan per definisjon ikke fortelle deg at den er utilgjengelig. Er
19
+ * den nede, kommer det ingenting derfra å rendre. Derfor eier komponenten
20
+ * alt innholdet, og `data-ignore-morph` hindrer at en senere patch river det
21
+ * bort i det øyeblikket forbindelsen er tilbake.
22
+ *
23
+ * ```ts
24
+ * <fs-connection-status {...fs.connectionStatus()} />
25
+ * ```
26
+ */
27
+ export declare const connectionStatus: ({ offlineText, onlineText, }?: ConnectionStatusOptions) => ConnectionStatusAttributes;
28
+ //# sourceMappingURL=connection-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-status.d.ts","sourceRoot":"","sources":["../../../../src/components/frittstaende/connection-status/connection-status.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,EAAG,sBAA+B,CAAA;AACtE,eAAO,MAAM,2BAA2B,EAAG,2BAAoC,CAAA;AAE/E,MAAM,MAAM,uBAAuB,GAAG;IACpC,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,OAAO,uBAAuB,CAAA;IACrC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,EAAE,EAAE,CAAA;CACxB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,+BAG9B,uBAA4B,KAAG,0BAM9B,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { attributes } from "../../css/shared.js";
2
+ export const CONNECTION_STATUS_CLASS = "fs-connection-status";
3
+ export const CONNECTION_STATUS_BAR_CLASS = "fs-connection-status__bar";
4
+ /**
5
+ * Attributtene for linja som sier fra at forbindelsen er borte.
6
+ *
7
+ * Serveren kan per definisjon ikke fortelle deg at den er utilgjengelig. Er
8
+ * den nede, kommer det ingenting derfra å rendre. Derfor eier komponenten
9
+ * alt innholdet, og `data-ignore-morph` hindrer at en senere patch river det
10
+ * bort i det øyeblikket forbindelsen er tilbake.
11
+ *
12
+ * ```ts
13
+ * <fs-connection-status {...fs.connectionStatus()} />
14
+ * ```
15
+ */
16
+ export const connectionStatus = ({ offlineText, onlineText, } = {}) => attributes({
17
+ class: CONNECTION_STATUS_CLASS,
18
+ "offline-text": offlineText,
19
+ "online-text": onlineText,
20
+ "data-ignore-morph": "",
21
+ });
22
+ //# sourceMappingURL=connection-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-status.js","sourceRoot":"","sources":["../../../../src/components/frittstaende/connection-status/connection-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,MAAM,CAAC,MAAM,uBAAuB,GAAG,sBAA+B,CAAA;AACtE,MAAM,CAAC,MAAM,2BAA2B,GAAG,2BAAoC,CAAA;AAgB/E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,WAAW,EACX,UAAU,MACiB,EAAE,EAA8B,EAAE,CAC7D,UAAU,CAAC;IACT,KAAK,EAAE,uBAAuB;IAC9B,cAAc,EAAE,WAAW;IAC3B,aAAa,EAAE,UAAU;IACzB,mBAAmB,EAAE,EAAW;CACjC,CAAC,CAAA"}
@@ -0,0 +1,54 @@
1
+ export declare const FS_CONNECTION_STATUS_TAG: "fs-connection-status";
2
+ /**
3
+ * Sier fra når forbindelsen til serveren er borte.
4
+ *
5
+ * Serveren kan per definisjon ikke fortelle deg dette. Er den utilgjengelig,
6
+ * kommer det ingenting derfra. Derfor er dette en frittstående komponent som
7
+ * eier alt innholdet sitt.
8
+ *
9
+ * Den er ikke pynt i en app som henter innhold fra serveren underveis. En
10
+ * side som mister strømmen sin ser helt normal ut, men er frosset, og en
11
+ * bruker som fyller ut et skjema inn i en død forbindelse mister alt.
12
+ *
13
+ * `navigator.onLine` sier bare om maskinen har et nettverk, ikke om serveren
14
+ * svarer. Derfor finnes `reportFailure()` og `reportSuccess()`, som appen
15
+ * kaller når et kall feiler eller lykkes.
16
+ *
17
+ * ```ts
18
+ * const status = document.querySelector("fs-connection-status")
19
+ * try {
20
+ * await fetch("/api/lagre", { method: "POST", body })
21
+ * status.reportSuccess()
22
+ * } catch {
23
+ * status.reportFailure()
24
+ * }
25
+ * ```
26
+ */
27
+ export declare class FsConnectionStatus extends HTMLElement {
28
+ static observedAttributes: string[];
29
+ private bar?;
30
+ private timer?;
31
+ /** Sant når appen har meldt at et kall feilet, uavhengig av navigator. */
32
+ private failing;
33
+ get offlineText(): string;
34
+ get onlineText(): string;
35
+ connectedCallback(): void;
36
+ disconnectedCallback(): void;
37
+ private ensureBar;
38
+ private showOffline;
39
+ private showOnline;
40
+ private handleOffline;
41
+ private handleOnline;
42
+ private emit;
43
+ /** Meld fra at et kall til serveren feilet. */
44
+ reportFailure(): void;
45
+ /** Meld fra at et kall til serveren lyktes igjen. */
46
+ reportSuccess(): void;
47
+ }
48
+ declare global {
49
+ interface HTMLElementTagNameMap {
50
+ "fs-connection-status": FsConnectionStatus;
51
+ }
52
+ }
53
+ export declare function defineFsConnectionStatus(tagName?: "fs-connection-status"): void;
54
+ //# sourceMappingURL=fs-connection-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-connection-status.d.ts","sourceRoot":"","sources":["../../../../src/components/frittstaende/connection-status/fs-connection-status.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,wBAAwB,EAAG,sBAA+B,CAAA;AAQvE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;IACjD,MAAM,CAAC,kBAAkB,WAAkC;IAE3D,OAAO,CAAC,GAAG,CAAC,CAAa;IACzB,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,0EAA0E;IAC1E,OAAO,CAAC,OAAO,CAAQ;IAEvB,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,iBAAiB,IAAI,IAAI;IAOzB,oBAAoB,IAAI,IAAI;IAO5B,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,aAAa,CAEpB;IAED,OAAO,CAAC,YAAY,CAGnB;IAED,OAAO,CAAC,IAAI;IAIZ,+CAA+C;IAC/C,aAAa,IAAI,IAAI;IAMrB,qDAAqD;IACrD,aAAa,IAAI,IAAI;CAKtB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,kBAAkB,CAAA;KAC3C;CACF;AAED,wBAAgB,wBAAwB,CACtC,OAAO,yBAA2B,GACjC,IAAI,CAIN"}
@@ -0,0 +1,123 @@
1
+ import { CONNECTION_STATUS_BAR_CLASS, CONNECTION_STATUS_CLASS, } from "./connection-status.js";
2
+ export const FS_CONNECTION_STATUS_TAG = "fs-connection-status";
3
+ const STANDARD_OFFLINE = "Ingen forbindelse. Det du skriver blir ikke lagret.";
4
+ const STANDARD_ONLINE = "Forbindelsen er tilbake.";
5
+ /** Hvor lenge «tilbake på nett» blir stående. */
6
+ const KVITTERING_MS = 4000;
7
+ /**
8
+ * Sier fra når forbindelsen til serveren er borte.
9
+ *
10
+ * Serveren kan per definisjon ikke fortelle deg dette. Er den utilgjengelig,
11
+ * kommer det ingenting derfra. Derfor er dette en frittstående komponent som
12
+ * eier alt innholdet sitt.
13
+ *
14
+ * Den er ikke pynt i en app som henter innhold fra serveren underveis. En
15
+ * side som mister strømmen sin ser helt normal ut, men er frosset, og en
16
+ * bruker som fyller ut et skjema inn i en død forbindelse mister alt.
17
+ *
18
+ * `navigator.onLine` sier bare om maskinen har et nettverk, ikke om serveren
19
+ * svarer. Derfor finnes `reportFailure()` og `reportSuccess()`, som appen
20
+ * kaller når et kall feiler eller lykkes.
21
+ *
22
+ * ```ts
23
+ * const status = document.querySelector("fs-connection-status")
24
+ * try {
25
+ * await fetch("/api/lagre", { method: "POST", body })
26
+ * status.reportSuccess()
27
+ * } catch {
28
+ * status.reportFailure()
29
+ * }
30
+ * ```
31
+ */
32
+ export class FsConnectionStatus extends HTMLElement {
33
+ constructor() {
34
+ super(...arguments);
35
+ /** Sant når appen har meldt at et kall feilet, uavhengig av navigator. */
36
+ this.failing = false;
37
+ this.handleOffline = () => {
38
+ this.showOffline();
39
+ };
40
+ this.handleOnline = () => {
41
+ if (this.failing)
42
+ return;
43
+ this.showOnline();
44
+ };
45
+ }
46
+ get offlineText() {
47
+ return this.getAttribute("offline-text") ?? STANDARD_OFFLINE;
48
+ }
49
+ get onlineText() {
50
+ return this.getAttribute("online-text") ?? STANDARD_ONLINE;
51
+ }
52
+ connectedCallback() {
53
+ this.classList.add(CONNECTION_STATUS_CLASS);
54
+ window.addEventListener("offline", this.handleOffline);
55
+ window.addEventListener("online", this.handleOnline);
56
+ if (!navigator.onLine)
57
+ this.showOffline();
58
+ }
59
+ disconnectedCallback() {
60
+ window.removeEventListener("offline", this.handleOffline);
61
+ window.removeEventListener("online", this.handleOnline);
62
+ if (this.timer)
63
+ window.clearTimeout(this.timer);
64
+ this.timer = undefined;
65
+ }
66
+ ensureBar() {
67
+ if (this.bar)
68
+ return this.bar;
69
+ const bar = document.createElement("div");
70
+ bar.className = CONNECTION_STATUS_BAR_CLASS;
71
+ // polite og ikke assertive: beskjeden er viktig, men skal ikke avbryte
72
+ // midt i et ord. Den blir stående til forbindelsen er tilbake.
73
+ bar.setAttribute("role", "status");
74
+ bar.setAttribute("aria-live", "polite");
75
+ this.append(bar);
76
+ this.bar = bar;
77
+ return bar;
78
+ }
79
+ showOffline() {
80
+ if (this.timer)
81
+ window.clearTimeout(this.timer);
82
+ const bar = this.ensureBar();
83
+ bar.dataset.state = "offline";
84
+ bar.textContent = this.offlineText;
85
+ this.emit("connection-lost");
86
+ }
87
+ showOnline() {
88
+ if (!this.bar)
89
+ return;
90
+ const bar = this.bar;
91
+ bar.dataset.state = "online";
92
+ bar.textContent = this.onlineText;
93
+ this.emit("connection-restored");
94
+ this.timer = window.setTimeout(() => {
95
+ bar.remove();
96
+ this.bar = undefined;
97
+ }, KVITTERING_MS);
98
+ }
99
+ emit(navn) {
100
+ this.dispatchEvent(new CustomEvent(navn, { bubbles: true, composed: true }));
101
+ }
102
+ /** Meld fra at et kall til serveren feilet. */
103
+ reportFailure() {
104
+ if (this.failing)
105
+ return;
106
+ this.failing = true;
107
+ this.showOffline();
108
+ }
109
+ /** Meld fra at et kall til serveren lyktes igjen. */
110
+ reportSuccess() {
111
+ if (!this.failing)
112
+ return;
113
+ this.failing = false;
114
+ this.showOnline();
115
+ }
116
+ }
117
+ FsConnectionStatus.observedAttributes = ["offline-text", "online-text"];
118
+ export function defineFsConnectionStatus(tagName = FS_CONNECTION_STATUS_TAG) {
119
+ if (!customElements.get(tagName)) {
120
+ customElements.define(tagName, FsConnectionStatus);
121
+ }
122
+ }
123
+ //# sourceMappingURL=fs-connection-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-connection-status.js","sourceRoot":"","sources":["../../../../src/components/frittstaende/connection-status/fs-connection-status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export declare const FS_SESSION_TIMEOUT_TAG: "fs-session-timeout";
2
+ /**
3
+ * Varsler før en innlogget økt går ut.
4
+ *
5
+ * Dette er tingen hver store organisasjon bygger selv og gjør feil. Enten
6
+ * kommer det ingen advarsel, og brukeren mister et halvutfylt søknadsskjema,
7
+ * eller så stjeler dialogen fokus midt i en setning, eller så leser
8
+ * skjermleseren nedtellingen hvert sekund.
9
+ *
10
+ * Komponenten er frittstående fordi nedtellingen er klientens klokke. Tallet
11
+ * endrer seg ut fra når brukeren sist rørte tastaturet, og det er ingenting
12
+ * en server kan sende. Den bærer heller ingen verdi inn i en innsending.
13
+ *
14
+ * Dialogen er en ekte `<dialog>` med `showModal()`, så fokusfellen, Escape og
15
+ * topplaget kommer fra nettleseren. Fokus går tilbake dit brukeren var.
16
+ *
17
+ * ```html
18
+ * <fs-session-timeout class="fs-session-timeout" warn-at="1500" expires-at="1800"
19
+ * data-ignore-morph></fs-session-timeout>
20
+ * ```
21
+ *
22
+ * ```ts
23
+ * document.querySelector("fs-session-timeout")
24
+ * .addEventListener("session-extend", () => fetch("/forleng", { method: "POST" }))
25
+ * ```
26
+ */
27
+ export declare class FsSessionTimeout extends HTMLElement {
28
+ static observedAttributes: string[];
29
+ private dialog?;
30
+ private countElement?;
31
+ private liveElement?;
32
+ private ticker?;
33
+ private lastActivity;
34
+ private forrigeFokus;
35
+ /** Sekunder uten aktivitet før varselet kommer. */
36
+ get warnAt(): number;
37
+ /** Sekunder uten aktivitet før økten er ute. */
38
+ get expiresAt(): number;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ private build;
42
+ private registerActivity;
43
+ private tick;
44
+ private open;
45
+ private close;
46
+ private emit;
47
+ /** Forlenger økten og lukker varselet. Kall den når serveren har svart. */
48
+ extend(): void;
49
+ }
50
+ declare global {
51
+ interface HTMLElementTagNameMap {
52
+ "fs-session-timeout": FsSessionTimeout;
53
+ }
54
+ }
55
+ export declare function defineFsSessionTimeout(tagName?: "fs-session-timeout"): void;
56
+ //# sourceMappingURL=fs-session-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-session-timeout.d.ts","sourceRoot":"","sources":["../../../../src/components/frittstaende/session-timeout/fs-session-timeout.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,sBAAsB,EAAG,oBAA6B,CAAA;AAmBnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,MAAM,CAAC,kBAAkB,WAA4B;IAErD,OAAO,CAAC,MAAM,CAAC,CAAmB;IAClC,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,YAAY,CAA2B;IAE/C,mDAAmD;IACnD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,gDAAgD;IAChD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,iBAAiB,IAAI,IAAI;IAUzB,oBAAoB,IAAI,IAAI;IAQ5B,OAAO,CAAC,KAAK;IA2Db,OAAO,CAAC,gBAAgB,CAGvB;IAED,OAAO,CAAC,IAAI;IAqBZ,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIZ,2EAA2E;IAC3E,MAAM,IAAI,IAAI;CAKf;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,gBAAgB,CAAA;KACvC;CACF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,uBAAyB,GAAG,IAAI,CAI7E"}
@@ -0,0 +1,179 @@
1
+ import { SESSION_TIMEOUT_CLASS, SESSION_TIMEOUT_DIALOG_CLASS, } from "./session-timeout.js";
2
+ export const FS_SESSION_TIMEOUT_TAG = "fs-session-timeout";
3
+ /** Sekundene som skal leses opp. Hvert sekund ville vært uutholdelig. */
4
+ const ANNONSER_VED = new Set([120, 60, 30, 10]);
5
+ function klokke(sekunder) {
6
+ const m = Math.floor(sekunder / 60);
7
+ const s = sekunder % 60;
8
+ return `${m}:${String(s).padStart(2, "0")}`;
9
+ }
10
+ function ord(sekunder) {
11
+ if (sekunder >= 60) {
12
+ const m = Math.round(sekunder / 60);
13
+ return m === 1 ? "ett minutt" : `${m} minutter`;
14
+ }
15
+ return sekunder === 1 ? "ett sekund" : `${sekunder} sekunder`;
16
+ }
17
+ /**
18
+ * Varsler før en innlogget økt går ut.
19
+ *
20
+ * Dette er tingen hver store organisasjon bygger selv og gjør feil. Enten
21
+ * kommer det ingen advarsel, og brukeren mister et halvutfylt søknadsskjema,
22
+ * eller så stjeler dialogen fokus midt i en setning, eller så leser
23
+ * skjermleseren nedtellingen hvert sekund.
24
+ *
25
+ * Komponenten er frittstående fordi nedtellingen er klientens klokke. Tallet
26
+ * endrer seg ut fra når brukeren sist rørte tastaturet, og det er ingenting
27
+ * en server kan sende. Den bærer heller ingen verdi inn i en innsending.
28
+ *
29
+ * Dialogen er en ekte `<dialog>` med `showModal()`, så fokusfellen, Escape og
30
+ * topplaget kommer fra nettleseren. Fokus går tilbake dit brukeren var.
31
+ *
32
+ * ```html
33
+ * <fs-session-timeout class="fs-session-timeout" warn-at="1500" expires-at="1800"
34
+ * data-ignore-morph></fs-session-timeout>
35
+ * ```
36
+ *
37
+ * ```ts
38
+ * document.querySelector("fs-session-timeout")
39
+ * .addEventListener("session-extend", () => fetch("/forleng", { method: "POST" }))
40
+ * ```
41
+ */
42
+ export class FsSessionTimeout extends HTMLElement {
43
+ constructor() {
44
+ super(...arguments);
45
+ this.lastActivity = Date.now();
46
+ this.forrigeFokus = null;
47
+ this.registerActivity = () => {
48
+ if (this.dialog?.open)
49
+ return;
50
+ this.lastActivity = Date.now();
51
+ };
52
+ }
53
+ /** Sekunder uten aktivitet før varselet kommer. */
54
+ get warnAt() {
55
+ return Number(this.getAttribute("warn-at")) || 25 * 60;
56
+ }
57
+ /** Sekunder uten aktivitet før økten er ute. */
58
+ get expiresAt() {
59
+ return Number(this.getAttribute("expires-at")) || 30 * 60;
60
+ }
61
+ connectedCallback() {
62
+ this.classList.add(SESSION_TIMEOUT_CLASS);
63
+ this.build();
64
+ for (const navn of ["pointerdown", "keydown", "scroll"]) {
65
+ document.addEventListener(navn, this.registerActivity, { passive: true });
66
+ }
67
+ this.ticker = window.setInterval(() => this.tick(), 1000);
68
+ }
69
+ disconnectedCallback() {
70
+ for (const navn of ["pointerdown", "keydown", "scroll"]) {
71
+ document.removeEventListener(navn, this.registerActivity);
72
+ }
73
+ if (this.ticker)
74
+ window.clearInterval(this.ticker);
75
+ this.ticker = undefined;
76
+ }
77
+ build() {
78
+ if (this.dialog)
79
+ return;
80
+ const dialog = document.createElement("dialog");
81
+ dialog.className = SESSION_TIMEOUT_DIALOG_CLASS;
82
+ // alertdialog og ikke dialog: dette er en avbrytelse brukeren ikke ba om,
83
+ // og da skal skjermleseren lese hele innholdet når den åpnes.
84
+ dialog.setAttribute("aria-labelledby", "fs-session-timeout-title");
85
+ dialog.setAttribute("role", "alertdialog");
86
+ const tittel = document.createElement("h2");
87
+ tittel.className = "fs-session-timeout__title";
88
+ tittel.id = "fs-session-timeout-title";
89
+ tittel.textContent = "Du blir snart logget ut";
90
+ const tekst = document.createElement("p");
91
+ tekst.className = "fs-session-timeout__text";
92
+ tekst.append("Vi logger deg ut om ");
93
+ const count = document.createElement("span");
94
+ count.className = "fs-session-timeout__count";
95
+ // Tallet oppdateres hvert sekund. Leses det opp hver gang, blir dialogen
96
+ // ubrukelig med skjermleser, så opplesningen skjer i live-området under.
97
+ count.setAttribute("aria-hidden", "true");
98
+ tekst.append(count, " for å beskytte opplysningene dine.");
99
+ const live = document.createElement("span");
100
+ live.className = "fs-sr-only";
101
+ live.setAttribute("role", "status");
102
+ live.setAttribute("aria-live", "polite");
103
+ const knapper = document.createElement("div");
104
+ knapper.className = "fs-session-timeout__actions";
105
+ const fortsett = document.createElement("button");
106
+ fortsett.type = "button";
107
+ fortsett.className = "fs-button";
108
+ fortsett.textContent = "Fortsett å være innlogget";
109
+ fortsett.addEventListener("click", () => this.extend());
110
+ const loggUt = document.createElement("button");
111
+ loggUt.type = "button";
112
+ loggUt.className = "fs-button";
113
+ loggUt.dataset.variant = "secondary";
114
+ loggUt.textContent = "Logg ut nå";
115
+ loggUt.addEventListener("click", () => {
116
+ this.close();
117
+ this.emit("session-logout");
118
+ });
119
+ knapper.append(fortsett, loggUt);
120
+ dialog.append(tittel, tekst, live, knapper);
121
+ this.append(dialog);
122
+ this.dialog = dialog;
123
+ this.countElement = count;
124
+ this.liveElement = live;
125
+ }
126
+ tick() {
127
+ const gått = Math.floor((Date.now() - this.lastActivity) / 1000);
128
+ const igjen = this.expiresAt - gått;
129
+ if (igjen <= 0) {
130
+ this.close();
131
+ this.emit("session-expired");
132
+ this.lastActivity = Date.now();
133
+ return;
134
+ }
135
+ if (gått < this.warnAt)
136
+ return;
137
+ if (!this.dialog?.open)
138
+ this.open();
139
+ if (this.countElement)
140
+ this.countElement.textContent = klokke(igjen);
141
+ if (this.liveElement && ANNONSER_VED.has(igjen)) {
142
+ this.liveElement.textContent = `Du blir logget ut om ${ord(igjen)}.`;
143
+ }
144
+ }
145
+ open() {
146
+ if (!this.dialog || this.dialog.open)
147
+ return;
148
+ // Fokus skal tilbake dit brukeren var. Uten dette starter neste
149
+ // tastetrykk på toppen av siden, midt i et skjema.
150
+ this.forrigeFokus = document.activeElement;
151
+ this.dialog.showModal();
152
+ this.emit("session-warn");
153
+ }
154
+ close() {
155
+ if (!this.dialog?.open)
156
+ return;
157
+ this.dialog.close();
158
+ if (this.liveElement)
159
+ this.liveElement.textContent = "";
160
+ this.forrigeFokus?.focus();
161
+ this.forrigeFokus = null;
162
+ }
163
+ emit(navn) {
164
+ this.dispatchEvent(new CustomEvent(navn, { bubbles: true, composed: true }));
165
+ }
166
+ /** Forlenger økten og lukker varselet. Kall den når serveren har svart. */
167
+ extend() {
168
+ this.lastActivity = Date.now();
169
+ this.close();
170
+ this.emit("session-extend");
171
+ }
172
+ }
173
+ FsSessionTimeout.observedAttributes = ["warn-at", "expires-at"];
174
+ export function defineFsSessionTimeout(tagName = FS_SESSION_TIMEOUT_TAG) {
175
+ if (!customElements.get(tagName)) {
176
+ customElements.define(tagName, FsSessionTimeout);
177
+ }
178
+ }
179
+ //# sourceMappingURL=fs-session-timeout.js.map