@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.
Files changed (116) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +22 -9
  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 +16 -25
  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
@@ -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
- }