@fristil/designsystem 0.3.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.
- package/CHANGELOG.md +121 -0
- package/README.md +22 -9
- package/dist/components/frittstaende/connection-status/connection-status.d.ts +28 -0
- package/dist/components/frittstaende/connection-status/connection-status.d.ts.map +1 -0
- package/dist/components/frittstaende/connection-status/connection-status.js +22 -0
- package/dist/components/frittstaende/connection-status/connection-status.js.map +1 -0
- package/dist/components/frittstaende/connection-status/fs-connection-status.d.ts +54 -0
- package/dist/components/frittstaende/connection-status/fs-connection-status.d.ts.map +1 -0
- package/dist/components/frittstaende/connection-status/fs-connection-status.js +123 -0
- package/dist/components/frittstaende/connection-status/fs-connection-status.js.map +1 -0
- package/dist/components/frittstaende/session-timeout/fs-session-timeout.d.ts +56 -0
- package/dist/components/frittstaende/session-timeout/fs-session-timeout.d.ts.map +1 -0
- package/dist/components/frittstaende/session-timeout/fs-session-timeout.js +179 -0
- package/dist/components/frittstaende/session-timeout/fs-session-timeout.js.map +1 -0
- package/dist/components/frittstaende/session-timeout/session-timeout.d.ts +28 -0
- package/dist/components/frittstaende/session-timeout/session-timeout.d.ts.map +1 -0
- package/dist/components/frittstaende/session-timeout/session-timeout.js +22 -0
- package/dist/components/frittstaende/session-timeout/session-timeout.js.map +1 -0
- package/dist/components/frittstaende/toast/fs-toast.d.ts +16 -22
- package/dist/components/frittstaende/toast/fs-toast.d.ts.map +1 -1
- package/dist/components/frittstaende/toast/fs-toast.js +38 -27
- package/dist/components/frittstaende/toast/fs-toast.js.map +1 -1
- package/dist/components/frittstaende/toast/toast.d.ts +34 -0
- package/dist/components/frittstaende/toast/toast.d.ts.map +1 -0
- package/dist/components/frittstaende/toast/toast.js +26 -0
- package/dist/components/frittstaende/toast/toast.js.map +1 -0
- package/dist/components/ramme/error-summary/error-summary.d.ts +39 -0
- package/dist/components/ramme/error-summary/error-summary.d.ts.map +1 -0
- package/dist/components/ramme/error-summary/error-summary.js +31 -0
- package/dist/components/ramme/error-summary/error-summary.js.map +1 -0
- package/dist/components/ramme/error-summary/fs-error-summary.d.ts +16 -42
- package/dist/components/ramme/error-summary/fs-error-summary.d.ts.map +1 -1
- package/dist/components/ramme/error-summary/fs-error-summary.js +57 -92
- package/dist/components/ramme/error-summary/fs-error-summary.js.map +1 -1
- package/dist/components/ramme/field/field-core.d.ts +31 -0
- package/dist/components/ramme/field/field-core.d.ts.map +1 -1
- package/dist/components/ramme/field/field-core.js +31 -0
- package/dist/components/ramme/field/field-core.js.map +1 -1
- package/dist/components/ramme/field/fs-field.d.ts +38 -40
- package/dist/components/ramme/field/fs-field.d.ts.map +1 -1
- package/dist/components/ramme/field/fs-field.js +127 -54
- package/dist/components/ramme/field/fs-field.js.map +1 -1
- package/dist/components/ramme/popover/fs-popover.d.ts +20 -27
- package/dist/components/ramme/popover/fs-popover.d.ts.map +1 -1
- package/dist/components/ramme/popover/fs-popover.js +68 -54
- package/dist/components/ramme/popover/fs-popover.js.map +1 -1
- package/dist/components/ramme/popover/popover.d.ts +43 -0
- package/dist/components/ramme/popover/popover.d.ts.map +1 -0
- package/dist/components/ramme/popover/popover.js +49 -0
- package/dist/components/ramme/popover/popover.js.map +1 -0
- package/dist/components/ramme/suggestion/fs-suggestion.d.ts +75 -0
- package/dist/components/ramme/suggestion/fs-suggestion.d.ts.map +1 -0
- package/dist/components/ramme/suggestion/fs-suggestion.js +232 -0
- package/dist/components/ramme/suggestion/fs-suggestion.js.map +1 -0
- package/dist/components/ramme/suggestion/suggestion.d.ts +97 -0
- package/dist/components/ramme/suggestion/suggestion.d.ts.map +1 -0
- package/dist/components/ramme/suggestion/suggestion.js +79 -0
- package/dist/components/ramme/suggestion/suggestion.js.map +1 -0
- package/dist/components/ramme/tabs/fs-tabs.d.ts +31 -36
- package/dist/components/ramme/tabs/fs-tabs.d.ts.map +1 -1
- package/dist/components/ramme/tabs/fs-tabs.js +62 -84
- package/dist/components/ramme/tabs/fs-tabs.js.map +1 -1
- package/dist/components/ramme/tabs/tabs.d.ts +50 -0
- package/dist/components/ramme/tabs/tabs.d.ts.map +1 -0
- package/dist/components/ramme/tabs/tabs.js +54 -0
- package/dist/components/ramme/tabs/tabs.js.map +1 -0
- package/dist/fs.d.ts +94 -0
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +42 -0
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/jsx/react.d.ts +35 -50
- package/dist/jsx/react.d.ts.map +1 -1
- package/dist/react.d.ts +142 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +71 -2
- package/dist/react.js.map +1 -1
- package/package.json +16 -25
- package/src/components/frittstaende/connection-status/connection-status.css +33 -0
- package/src/components/frittstaende/connection-status/connection-status.ts +41 -0
- package/src/components/frittstaende/connection-status/fs-connection-status.ts +144 -0
- package/src/components/frittstaende/session-timeout/fs-session-timeout.ts +212 -0
- package/src/components/frittstaende/session-timeout/session-timeout.css +43 -0
- package/src/components/frittstaende/session-timeout/session-timeout.ts +42 -0
- package/src/components/frittstaende/toast/fs-toast.ts +39 -30
- package/src/components/frittstaende/toast/toast.ts +45 -0
- package/src/components/ramme/error-summary/error-summary.ts +55 -0
- package/src/components/ramme/error-summary/fs-error-summary.ts +62 -119
- package/src/components/ramme/field/field-core.ts +32 -0
- package/src/components/ramme/field/fs-field.ts +144 -61
- package/src/components/ramme/popover/fs-popover.ts +78 -72
- package/src/components/ramme/popover/popover.ts +79 -0
- package/src/components/ramme/suggestion/fs-suggestion.ts +265 -0
- package/src/components/ramme/suggestion/suggestion.ts +105 -0
- package/src/components/ramme/tabs/fs-tabs.ts +67 -98
- package/src/components/ramme/tabs/tabs.ts +104 -0
- package/dist/components/frittstaende/calendar/fs-calendar.d.ts +0 -52
- package/dist/components/frittstaende/calendar/fs-calendar.d.ts.map +0 -1
- package/dist/components/frittstaende/calendar/fs-calendar.js +0 -462
- package/dist/components/frittstaende/calendar/fs-calendar.js.map +0 -1
- package/dist/components/frittstaende/date-field/fs-date-field.d.ts +0 -110
- package/dist/components/frittstaende/date-field/fs-date-field.d.ts.map +0 -1
- package/dist/components/frittstaende/date-field/fs-date-field.js +0 -264
- package/dist/components/frittstaende/date-field/fs-date-field.js.map +0 -1
- package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts +0 -133
- package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts.map +0 -1
- package/dist/components/frittstaende/suggestion/fs-suggestion.js +0 -280
- package/dist/components/frittstaende/suggestion/fs-suggestion.js.map +0 -1
- package/src/components/frittstaende/calendar/fs-calendar.ts +0 -525
- package/src/components/frittstaende/date-field/date-field.css +0 -54
- package/src/components/frittstaende/date-field/fs-date-field.ts +0 -309
- package/src/components/frittstaende/suggestion/fs-suggestion.ts +0 -333
- /package/src/components/{frittstaende → ramme}/suggestion/suggestion.css +0 -0
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
import { css, html, LitElement } from "lit"
|
|
2
|
-
|
|
3
|
-
export const FS_CALENDAR_TAG = "fs-calendar" as const
|
|
4
|
-
|
|
5
|
-
type CalendarCell = {
|
|
6
|
-
date: Date
|
|
7
|
-
iso: string
|
|
8
|
-
inMonth: boolean
|
|
9
|
-
isToday: boolean
|
|
10
|
-
isSelected: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Deler dagene inn i uker på sju.
|
|
15
|
-
*
|
|
16
|
-
* role="grid" krever role="row" mellom seg og cellene sine. Uten radene
|
|
17
|
-
* melder skjermlesere rutenettet som tomt, og cellene som løsrevne knapper.
|
|
18
|
-
*/
|
|
19
|
-
function splitInWeeks(cells: CalendarCell[]): CalendarCell[][] {
|
|
20
|
-
const weeks: CalendarCell[][] = []
|
|
21
|
-
for (let i = 0; i < cells.length; i += 7) {
|
|
22
|
-
weeks.push(cells.slice(i, i + 7))
|
|
23
|
-
}
|
|
24
|
-
return weeks
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Delnavnene på én dagknapp.
|
|
29
|
-
*
|
|
30
|
-
* Tilstanden må være egne delnavn og ikke bare `data-*`. En attributtselektor
|
|
31
|
-
* etter `::part()` treffer ikke. `::part(day)[data-selected="true"]` er målt
|
|
32
|
-
* uten virkning, så uten `day-selected` ville en konsument ikke hatt noen vei
|
|
33
|
-
* til den valgte dagen utenfra.
|
|
34
|
-
*/
|
|
35
|
-
function dayParts(cell: CalendarCell): string {
|
|
36
|
-
const names = ["day"]
|
|
37
|
-
if (!cell.inMonth) names.push("day-outside")
|
|
38
|
-
if (cell.isToday) names.push("day-today")
|
|
39
|
-
if (cell.isSelected) names.push("day-selected")
|
|
40
|
-
return names.join(" ")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const weekdayFormatter = new Intl.DateTimeFormat("nb-NO", {
|
|
44
|
-
weekday: "short",
|
|
45
|
-
timeZone: "UTC",
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const monthFormatter = new Intl.DateTimeFormat("nb-NO", {
|
|
49
|
-
month: "long",
|
|
50
|
-
year: "numeric",
|
|
51
|
-
timeZone: "UTC",
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
function parseIsoDate(value: string | undefined): Date | null {
|
|
55
|
-
if (!value) return null
|
|
56
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
|
|
57
|
-
if (!match) return null
|
|
58
|
-
|
|
59
|
-
const year = Number(match[1])
|
|
60
|
-
const month = Number(match[2]) - 1
|
|
61
|
-
const day = Number(match[3])
|
|
62
|
-
return new Date(Date.UTC(year, month, day))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function formatIsoDate(date: Date): string {
|
|
66
|
-
return date.toISOString().slice(0, 10)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function startOfMonth(date: Date): Date {
|
|
70
|
-
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function addDays(date: Date, delta: number): Date {
|
|
74
|
-
return new Date(
|
|
75
|
-
Date.UTC(
|
|
76
|
-
date.getUTCFullYear(),
|
|
77
|
-
date.getUTCMonth(),
|
|
78
|
-
date.getUTCDate() + delta,
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function addMonths(date: Date, delta: number): Date {
|
|
84
|
-
return new Date(
|
|
85
|
-
Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + delta, 1),
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function mondayBasedWeekday(date: Date): number {
|
|
90
|
-
return (date.getUTCDay() + 6) % 7
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getToday(): Date {
|
|
94
|
-
const today = new Date()
|
|
95
|
-
return new Date(
|
|
96
|
-
Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()),
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function getCalendarCells(
|
|
101
|
-
monthDate: Date,
|
|
102
|
-
selectedIso?: string,
|
|
103
|
-
): CalendarCell[] {
|
|
104
|
-
const selectedDate = parseIsoDate(selectedIso)
|
|
105
|
-
const todayIso = formatIsoDate(getToday())
|
|
106
|
-
const monthStart = startOfMonth(monthDate)
|
|
107
|
-
const firstVisible = addDays(monthStart, -mondayBasedWeekday(monthStart))
|
|
108
|
-
|
|
109
|
-
return Array.from({ length: 42 }, (_, index) => {
|
|
110
|
-
const date = addDays(firstVisible, index)
|
|
111
|
-
const iso = formatIsoDate(date)
|
|
112
|
-
return {
|
|
113
|
-
date,
|
|
114
|
-
iso,
|
|
115
|
-
inMonth: date.getUTCMonth() === monthStart.getUTCMonth(),
|
|
116
|
-
isToday: iso === todayIso,
|
|
117
|
-
isSelected: selectedDate ? iso === formatIsoDate(selectedDate) : false,
|
|
118
|
-
}
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export class FsCalendar extends LitElement {
|
|
123
|
-
static properties = {
|
|
124
|
-
open: { type: Boolean, reflect: true },
|
|
125
|
-
disabled: { type: Boolean, reflect: true },
|
|
126
|
-
value: { type: String, reflect: true },
|
|
127
|
-
triggerHidden: {
|
|
128
|
-
type: Boolean,
|
|
129
|
-
reflect: true,
|
|
130
|
-
attribute: "trigger-hidden",
|
|
131
|
-
},
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
static styles = css`
|
|
135
|
-
:host {
|
|
136
|
-
display: inline-block;
|
|
137
|
-
font-family: inherit;
|
|
138
|
-
color: var(--semantic-page-foreground);
|
|
139
|
-
position: relative;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.trigger {
|
|
143
|
-
display: inline-grid;
|
|
144
|
-
place-items: center;
|
|
145
|
-
min-width: var(--fs-calendar-trigger-size, var(--size-10));
|
|
146
|
-
min-height: var(--fs-calendar-trigger-size, var(--size-10));
|
|
147
|
-
padding: var(--fs-calendar-trigger-padding, 0 var(--size-3));
|
|
148
|
-
font-family: inherit;
|
|
149
|
-
font-size: var(--font-size-m);
|
|
150
|
-
line-height: 1;
|
|
151
|
-
color: var(--semantic-page-foreground);
|
|
152
|
-
background: var(--semantic-page-background);
|
|
153
|
-
border: 1px solid var(--semantic-field-border);
|
|
154
|
-
border-radius: var(--fs-calendar-radius, var(--size-1));
|
|
155
|
-
cursor: pointer;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.trigger:hover:not(:disabled) {
|
|
159
|
-
background: var(--semantic-interactive-background);
|
|
160
|
-
border-color: var(--semantic-interactive-main);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.trigger:focus-visible,
|
|
164
|
-
.month-button:focus-visible,
|
|
165
|
-
.day:focus-visible {
|
|
166
|
-
outline: 2px solid var(--semantic-interactive-main);
|
|
167
|
-
outline-offset: 2px;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.popup {
|
|
171
|
-
position: absolute;
|
|
172
|
-
inset-inline-start: 0;
|
|
173
|
-
inset-block-start: calc(100% + var(--size-1));
|
|
174
|
-
z-index: 30;
|
|
175
|
-
width: var(--fs-calendar-popup-width, min(20rem, 92vw));
|
|
176
|
-
padding: var(--fs-calendar-popup-padding, var(--size-3));
|
|
177
|
-
border: 1px solid var(--semantic-field-border);
|
|
178
|
-
border-radius: var(--fs-calendar-radius, var(--size-1));
|
|
179
|
-
background: var(--semantic-page-background);
|
|
180
|
-
box-shadow: var(--semantic-shadow-overlay);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.popup[hidden] {
|
|
184
|
-
display: none;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.popup-header {
|
|
188
|
-
display: flex;
|
|
189
|
-
align-items: center;
|
|
190
|
-
justify-content: space-between;
|
|
191
|
-
gap: var(--size-2);
|
|
192
|
-
margin-bottom: var(--size-2);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.month-label {
|
|
196
|
-
font-size: var(--font-size-s);
|
|
197
|
-
font-weight: 700;
|
|
198
|
-
text-transform: capitalize;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.month-controls {
|
|
202
|
-
display: inline-flex;
|
|
203
|
-
gap: var(--size-1);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.month-button,
|
|
207
|
-
.day {
|
|
208
|
-
font-family: inherit;
|
|
209
|
-
border: 1px solid var(--semantic-field-border);
|
|
210
|
-
background: var(--semantic-page-background);
|
|
211
|
-
color: var(--semantic-page-foreground);
|
|
212
|
-
border-radius: var(--fs-calendar-radius, var(--size-1));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.month-button {
|
|
216
|
-
min-width: var(--fs-calendar-day-size, var(--size-8));
|
|
217
|
-
min-height: var(--fs-calendar-day-size, var(--size-8));
|
|
218
|
-
cursor: pointer;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.weekday-row,
|
|
222
|
-
.day-grid {
|
|
223
|
-
display: grid;
|
|
224
|
-
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
225
|
-
gap: var(--size-1);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/* Ukeradene finnes for ARIA-strukturens skyld. display: contents lar
|
|
229
|
-
dagene ligge rett i rutenettet, så oppsettet er uendret. */
|
|
230
|
-
.week {
|
|
231
|
-
display: contents;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.weekday {
|
|
235
|
-
text-align: center;
|
|
236
|
-
font-size: var(--font-size-xxs);
|
|
237
|
-
font-weight: 700;
|
|
238
|
-
text-transform: uppercase;
|
|
239
|
-
letter-spacing: 0.04em;
|
|
240
|
-
color: var(--semantic-muted-foreground);
|
|
241
|
-
padding-block: var(--size-1);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.day {
|
|
245
|
-
min-height: var(--fs-calendar-day-size, var(--size-8));
|
|
246
|
-
cursor: pointer;
|
|
247
|
-
font-size: var(--font-size-xs);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/* Dagene fra nabomåneden er dempet, men fullt klikkbare. De er altså
|
|
251
|
-
ikke deaktiverte kontroller, og må derfor ha lesbar kontrast. */
|
|
252
|
-
.day[data-outside="true"] {
|
|
253
|
-
color: var(--semantic-neutral-foreground);
|
|
254
|
-
background: var(--semantic-neutral-background);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.day[data-today="true"] {
|
|
258
|
-
border-color: var(--semantic-interactive-main);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.day[data-selected="true"] {
|
|
262
|
-
background: var(--semantic-interactive-main);
|
|
263
|
-
color: var(--semantic-interactive-contrast);
|
|
264
|
-
border-color: var(--semantic-interactive-main);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/*
|
|
268
|
-
* Høykontrast: egne farger overstyres i modusen, så valgt dag og de andre
|
|
269
|
-
* blir like. Systemfargene slipper gjennom, og Highlight med
|
|
270
|
-
* HighlightText er paret operativsystemet bruker til noe som er valgt.
|
|
271
|
-
*
|
|
272
|
-
* Merk: ingen backtick-tegn i kommentarer her. De lukker malen, og resten
|
|
273
|
-
* av fila blir lest som JavaScript.
|
|
274
|
-
*/
|
|
275
|
-
@media (forced-colors: active) {
|
|
276
|
-
.day[data-selected="true"] {
|
|
277
|
-
background: Highlight;
|
|
278
|
-
color: HighlightText;
|
|
279
|
-
border-color: Highlight;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
:host([disabled]) .trigger {
|
|
284
|
-
background: var(--semantic-disabled-background);
|
|
285
|
-
color: var(--semantic-disabled-foreground);
|
|
286
|
-
border-color: var(--semantic-disabled-background);
|
|
287
|
-
/* Knappen er en ekte <button disabled>; pointer-events: none ville
|
|
288
|
-
bare hindret markøren i å vises. */
|
|
289
|
-
cursor: not-allowed;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
:host([trigger-hidden]) .trigger {
|
|
293
|
-
display: none;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
:host([trigger-hidden]) .popup {
|
|
297
|
-
inset-block-start: var(--size-1);
|
|
298
|
-
}
|
|
299
|
-
`
|
|
300
|
-
|
|
301
|
-
/** @attr */
|
|
302
|
-
open = false
|
|
303
|
-
/** @attr */
|
|
304
|
-
disabled = false
|
|
305
|
-
/** @attr */
|
|
306
|
-
value = ""
|
|
307
|
-
/** @attr trigger-hidden */
|
|
308
|
-
triggerHidden = false
|
|
309
|
-
|
|
310
|
-
private viewMonth = getToday()
|
|
311
|
-
|
|
312
|
-
connectedCallback() {
|
|
313
|
-
super.connectedCallback()
|
|
314
|
-
this.ownerDocument.addEventListener("click", this.handleDocumentClick)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
disconnectedCallback() {
|
|
318
|
-
this.ownerDocument.removeEventListener("click", this.handleDocumentClick)
|
|
319
|
-
super.disconnectedCallback()
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
updated(changed: Map<string, unknown>) {
|
|
323
|
-
if (changed.has("value")) {
|
|
324
|
-
this.viewMonth = parseIsoDate(this.value) ?? this.viewMonth
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
render() {
|
|
329
|
-
const monthCells = this.open
|
|
330
|
-
? getCalendarCells(this.viewMonth, this.value)
|
|
331
|
-
: []
|
|
332
|
-
|
|
333
|
-
return html`
|
|
334
|
-
<button
|
|
335
|
-
class="trigger"
|
|
336
|
-
part="trigger"
|
|
337
|
-
type="button"
|
|
338
|
-
aria-label="Åpne kalender"
|
|
339
|
-
aria-expanded=${this.open ? "true" : "false"}
|
|
340
|
-
?disabled=${this.disabled}
|
|
341
|
-
@click=${this.togglePopup}
|
|
342
|
-
>
|
|
343
|
-
📅
|
|
344
|
-
</button>
|
|
345
|
-
|
|
346
|
-
<div class="popup" part="popup" ?hidden=${!this.open} @keydown=${this.handlePopupKeydown}>
|
|
347
|
-
<div class="popup-header" part="popup-header">
|
|
348
|
-
<div class="month-label" part="month-label" aria-live="polite">${monthFormatter.format(this.viewMonth)}</div>
|
|
349
|
-
<div class="month-controls" part="month-controls">
|
|
350
|
-
<button class="month-button" part="month-button" type="button" aria-label="Forrige måned" @click=${this.goToPreviousMonth}>‹</button>
|
|
351
|
-
<button class="month-button" part="month-button" type="button" aria-label="Neste måned" @click=${this.goToNextMonth}>›</button>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
|
|
355
|
-
<div class="weekday-row" part="weekday-row" aria-hidden="true">
|
|
356
|
-
${[0, 1, 2, 3, 4, 5, 6].map(
|
|
357
|
-
(index) =>
|
|
358
|
-
html`<div class="weekday" part="weekday">${weekdayFormatter.format(new Date(Date.UTC(2024, 0, 1 + index)))}</div>`,
|
|
359
|
-
)}
|
|
360
|
-
</div>
|
|
361
|
-
|
|
362
|
-
<div class="day-grid" part="day-grid" role="grid" aria-label=${monthFormatter.format(this.viewMonth)}>
|
|
363
|
-
${splitInWeeks(monthCells).map(
|
|
364
|
-
(uke) => html`
|
|
365
|
-
<div class="week" role="row">
|
|
366
|
-
${uke.map(
|
|
367
|
-
(cell) => html`
|
|
368
|
-
<button
|
|
369
|
-
class="day"
|
|
370
|
-
part=${dayParts(cell)}
|
|
371
|
-
type="button"
|
|
372
|
-
role="gridcell"
|
|
373
|
-
data-date=${cell.iso}
|
|
374
|
-
data-outside=${String(!cell.inMonth)}
|
|
375
|
-
data-today=${String(cell.isToday)}
|
|
376
|
-
data-selected=${String(cell.isSelected)}
|
|
377
|
-
aria-selected=${String(cell.isSelected)}
|
|
378
|
-
@click=${this.selectCalendarDate}
|
|
379
|
-
>
|
|
380
|
-
${cell.date.getUTCDate()}
|
|
381
|
-
</button>
|
|
382
|
-
`,
|
|
383
|
-
)}
|
|
384
|
-
</div>
|
|
385
|
-
`,
|
|
386
|
-
)}
|
|
387
|
-
</div>
|
|
388
|
-
</div>
|
|
389
|
-
`
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
openPopup() {
|
|
393
|
-
if (this.disabled) return
|
|
394
|
-
this.viewMonth = parseIsoDate(this.value) ?? getToday()
|
|
395
|
-
this.open = true
|
|
396
|
-
this.requestUpdate()
|
|
397
|
-
queueMicrotask(() => {
|
|
398
|
-
const selected = this.renderRoot.querySelector<HTMLButtonElement>(
|
|
399
|
-
`.day[data-selected="true"]`,
|
|
400
|
-
)
|
|
401
|
-
selected?.focus()
|
|
402
|
-
})
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
closePopup() {
|
|
406
|
-
if (!this.open) return
|
|
407
|
-
|
|
408
|
-
// Panelet skjules med [hidden]. Står fokus på en dag inne i det, mister
|
|
409
|
-
// nettleseren fokus til <body>, og en tastaturbruker havner på toppen av
|
|
410
|
-
// siden. Fokus skal derfor tilbake til knappen som åpnet panelet.
|
|
411
|
-
// Lukkes panelet fordi brukeren klikket eller tabbet ut, står fokus
|
|
412
|
-
// allerede et annet sted, og da skal vi ikke rive det til oss.
|
|
413
|
-
const fokusStoInniPanelet =
|
|
414
|
-
(this.renderRoot as ShadowRoot).activeElement !== null
|
|
415
|
-
|
|
416
|
-
this.open = false
|
|
417
|
-
this.requestUpdate()
|
|
418
|
-
|
|
419
|
-
if (fokusStoInniPanelet) {
|
|
420
|
-
queueMicrotask(() => {
|
|
421
|
-
this.renderRoot.querySelector<HTMLButtonElement>(".trigger")?.focus()
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private togglePopup = () => {
|
|
427
|
-
if (this.open) {
|
|
428
|
-
this.closePopup()
|
|
429
|
-
} else {
|
|
430
|
-
this.openPopup()
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
private goToPreviousMonth = () => {
|
|
435
|
-
this.viewMonth = addMonths(this.viewMonth, -1)
|
|
436
|
-
this.requestUpdate()
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private goToNextMonth = () => {
|
|
440
|
-
this.viewMonth = addMonths(this.viewMonth, 1)
|
|
441
|
-
this.requestUpdate()
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private selectCalendarDate = (event: Event) => {
|
|
445
|
-
const target = event.currentTarget as HTMLButtonElement | null
|
|
446
|
-
const iso = target?.dataset.date
|
|
447
|
-
if (!iso) return
|
|
448
|
-
|
|
449
|
-
this.value = iso
|
|
450
|
-
this.viewMonth = parseIsoDate(iso) ?? this.viewMonth
|
|
451
|
-
|
|
452
|
-
// Lukk før hendelsene sendes. closePopup køer et mikrotaskkall som
|
|
453
|
-
// flytter fokus til knappen. En lytter som selv vil flytte fokus, slik
|
|
454
|
-
// fs-date-field gjør til inputfeltet, køer sitt etterpå og vinner.
|
|
455
|
-
this.closePopup()
|
|
456
|
-
|
|
457
|
-
this.dispatchEvent(
|
|
458
|
-
new CustomEvent("date-select", {
|
|
459
|
-
detail: { value: iso },
|
|
460
|
-
bubbles: true,
|
|
461
|
-
composed: true,
|
|
462
|
-
}),
|
|
463
|
-
)
|
|
464
|
-
this.dispatchEvent(new Event("input", { bubbles: true, composed: true }))
|
|
465
|
-
this.dispatchEvent(new Event("change", { bubbles: true, composed: true }))
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
private handlePopupKeydown = (event: KeyboardEvent) => {
|
|
469
|
-
if (event.key === "Escape") {
|
|
470
|
-
event.preventDefault()
|
|
471
|
-
this.closePopup()
|
|
472
|
-
return
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const current = event.target as HTMLButtonElement | null
|
|
476
|
-
if (!current?.classList.contains("day")) return
|
|
477
|
-
|
|
478
|
-
const iso = current.dataset.date
|
|
479
|
-
if (!iso) return
|
|
480
|
-
|
|
481
|
-
const targetDate = parseIsoDate(iso)
|
|
482
|
-
if (!targetDate) return
|
|
483
|
-
|
|
484
|
-
let nextDate: Date | null = null
|
|
485
|
-
if (event.key === "ArrowLeft") nextDate = addDays(targetDate, -1)
|
|
486
|
-
if (event.key === "ArrowRight") nextDate = addDays(targetDate, 1)
|
|
487
|
-
if (event.key === "ArrowUp") nextDate = addDays(targetDate, -7)
|
|
488
|
-
if (event.key === "ArrowDown") nextDate = addDays(targetDate, 7)
|
|
489
|
-
if (event.key === "Home") nextDate = startOfMonth(targetDate)
|
|
490
|
-
if (event.key === "End")
|
|
491
|
-
nextDate = addDays(addMonths(startOfMonth(targetDate), 1), -1)
|
|
492
|
-
|
|
493
|
-
if (!nextDate) return
|
|
494
|
-
|
|
495
|
-
event.preventDefault()
|
|
496
|
-
this.viewMonth = startOfMonth(nextDate)
|
|
497
|
-
this.requestUpdate()
|
|
498
|
-
|
|
499
|
-
queueMicrotask(() => {
|
|
500
|
-
const nextButton = this.renderRoot.querySelector<HTMLButtonElement>(
|
|
501
|
-
`.day[data-date="${formatIsoDate(nextDate)}"]`,
|
|
502
|
-
)
|
|
503
|
-
nextButton?.focus()
|
|
504
|
-
})
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
private handleDocumentClick = (event: Event) => {
|
|
508
|
-
if (!this.open) return
|
|
509
|
-
const path = event.composedPath()
|
|
510
|
-
if (path.includes(this)) return
|
|
511
|
-
this.closePopup()
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
declare global {
|
|
516
|
-
interface HTMLElementTagNameMap {
|
|
517
|
-
"fs-calendar": FsCalendar
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export function defineFsCalendar(tagName = FS_CALENDAR_TAG): void {
|
|
522
|
-
if (!customElements.get(tagName)) {
|
|
523
|
-
customElements.define(tagName, FsCalendar)
|
|
524
|
-
}
|
|
525
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
@import "../../css/label/label.css";
|
|
2
|
-
@import "../../css/input/input.css";
|
|
3
|
-
@import "../../css/help-text/help-text.css";
|
|
4
|
-
@import "../../css/error-text/error-text.css";
|
|
5
|
-
|
|
6
|
-
@layer fristil {
|
|
7
|
-
/* Oppsettet lå tidligere i style=-attributter på elementene. Inline stil
|
|
8
|
-
slår alt en konsument kan skrive uten !important, så komponenten var i
|
|
9
|
-
praksis låst. Her er den vanlig CSS i laget vårt, som alt annet. */
|
|
10
|
-
|
|
11
|
-
.fs-date-field__field {
|
|
12
|
-
position: relative;
|
|
13
|
-
display: inline-grid;
|
|
14
|
-
width: 100%;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* Plass til ikonknappen inne i feltet */
|
|
18
|
-
.fs-date-field__field .fs-input {
|
|
19
|
-
padding-inline-end: var(--fs-date-field-icon-space, var(--size-10));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.fs-date-field__icon-btn {
|
|
23
|
-
position: absolute;
|
|
24
|
-
inset-block-start: 50%;
|
|
25
|
-
inset-inline-end: var(--size-2);
|
|
26
|
-
transform: translateY(-50%);
|
|
27
|
-
display: inline-grid;
|
|
28
|
-
place-items: center;
|
|
29
|
-
width: var(--fs-date-field-icon-size, var(--size-7));
|
|
30
|
-
height: var(--fs-date-field-icon-size, var(--size-7));
|
|
31
|
-
padding: 0;
|
|
32
|
-
border: none;
|
|
33
|
-
background: transparent;
|
|
34
|
-
color: var(--semantic-page-foreground);
|
|
35
|
-
border-radius: var(--fs-date-field-icon-radius, var(--size-1));
|
|
36
|
-
cursor: pointer;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.fs-date-field__icon-btn:disabled {
|
|
40
|
-
color: var(--semantic-disabled-foreground);
|
|
41
|
-
cursor: not-allowed;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* Kalenderen er et null-stort feste panelet henger under, slik at panelet
|
|
45
|
-
ikke påvirker høyden på feltet. */
|
|
46
|
-
.fs-date-field__calendar {
|
|
47
|
-
position: absolute;
|
|
48
|
-
inset-block-start: 100%;
|
|
49
|
-
inset-inline-start: 0;
|
|
50
|
-
width: 0;
|
|
51
|
-
height: 0;
|
|
52
|
-
overflow: visible;
|
|
53
|
-
}
|
|
54
|
-
}
|