scb-wc-test 0.1.95 → 0.1.97

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.
@@ -0,0 +1,1922 @@
1
+ /* Kopplar ihop SCB-webbkomponenter med Blazor genom att normalisera events.
2
+ - Lyssnar på komponenternas egna events i DOM.
3
+ - Hittar berörd komponent via composedPath eller closest.
4
+ - Speglar relevanta värden från event.detail till motsvarande egenskap på elementet.
5
+ - Skickar ett Blazor-vänligt event name som går att använda med @on… i Razor.
6
+ - Använder MutationObserver för att skicka *change-events när "viktiga" attribut ändras.
7
+ */
8
+ (function () {
9
+ 'use strict';
10
+
11
+ /* Hjälpfunktion som letar upp närmaste element i eventets composedPath
12
+ som matchar en selektor (eller taggnamn). */
13
+ function closestInPath(selector, ev) {
14
+ if (ev.composedPath) {
15
+ const path = ev.composedPath();
16
+ for (const n of path) {
17
+ if (
18
+ n instanceof Element &&
19
+ (n.matches(selector) ||
20
+ (n.tagName && n.tagName.toLowerCase() === selector))
21
+ ) {
22
+ return n;
23
+ }
24
+ }
25
+ }
26
+ const t = ev.target;
27
+ return t instanceof Element ? t.closest(selector) : null;
28
+ }
29
+
30
+ /* Hjälpfunktion som skapar ett nytt event på elementet.
31
+ Om källan är ett CustomEvent så behålls detail i det nya eventet. */
32
+ function emit(el, name, srcEv) {
33
+ if (srcEv && typeof srcEv === 'object' && 'detail' in srcEv) {
34
+ el.dispatchEvent(
35
+ new CustomEvent(name, {
36
+ bubbles: true,
37
+ composed: true,
38
+ detail: srcEv.detail,
39
+ }),
40
+ );
41
+ } else {
42
+ el.dispatchEvent(new Event(name, { bubbles: true, composed: true }));
43
+ }
44
+ }
45
+
46
+ /* Mirror functions för scb-header-events.
47
+ Håller headerns attribut i synk med interna properties och inkommande events. */
48
+ function mirrorHeaderTab(el, ev) {
49
+ const i =
50
+ (ev && ev.detail && (ev.detail.index ?? ev.detail.value)) ?? 0;
51
+ try {
52
+ el.activeTab = i;
53
+ } catch (_) {}
54
+ el.setAttribute('active-tab', String(i));
55
+ }
56
+
57
+ // Markerar om headerns drawer är öppen eller stängd utan att påverka konfigurationen
58
+ function mirrorHeaderDrawer(el, isOpen) {
59
+ if (isOpen) el.setAttribute('drawer-open', '');
60
+ else el.removeAttribute('drawer-open');
61
+ }
62
+
63
+ function mirrorHeaderSearch(el, ev) {
64
+ const v = (ev && ev.detail && (ev.detail.value ?? ev.detail.text)) ?? '';
65
+ try {
66
+ el.searchText = v;
67
+ } catch (_) {}
68
+ if (!v) el.removeAttribute('search-text');
69
+ else el.setAttribute('search-text', String(v));
70
+ }
71
+
72
+ /* Regler som beskriver hur komponenternas egna events ska översättas
73
+ till enklare, Blazor-vänliga events (och ibland speglas till properties). */
74
+ const RULES = [
75
+ // scb-header med mirror fucntions som uppdaterar attribut och state
76
+ {
77
+ sel: 'scb-header',
78
+ from: 'tab-change',
79
+ to: 'tabchange',
80
+ mirror: mirrorHeaderTab,
81
+ },
82
+ {
83
+ sel: 'scb-header',
84
+ from: 'drawer-open',
85
+ to: 'draweropen',
86
+ mirror: (el) => mirrorHeaderDrawer(el, true),
87
+ },
88
+ {
89
+ sel: 'scb-header',
90
+ from: 'drawer-close',
91
+ to: 'draweropen',
92
+ mirror: (el) => mirrorHeaderDrawer(el, false),
93
+ },
94
+ { sel: 'scb-header', from: 'drawer-select', to: 'select' },
95
+ { sel: 'scb-header', from: 'menu-click', to: 'menuclick' },
96
+ {
97
+ sel: 'scb-header',
98
+ from: 'search-input',
99
+ to: 'search',
100
+ mirror: mirrorHeaderSearch,
101
+ },
102
+ {
103
+ sel: 'scb-header',
104
+ from: 'search-change',
105
+ to: 'search',
106
+ mirror: mirrorHeaderSearch,
107
+ },
108
+ {
109
+ sel: 'scb-header',
110
+ from: 'search-text-change',
111
+ to: 'search',
112
+ mirror: mirrorHeaderSearch,
113
+ },
114
+ {
115
+ sel: 'scb-header',
116
+ from: 'search-click',
117
+ to: 'search',
118
+ mirror: mirrorHeaderSearch,
119
+ },
120
+
121
+ // scb-accordion-item
122
+ // Lyssnar på open-changed, uppdaterar egenskapen open och skickar openchange
123
+ {
124
+ sel: 'scb-accordion-item',
125
+ from: 'open-changed',
126
+ to: 'openchange',
127
+ prop: 'open',
128
+ },
129
+
130
+ // scb-app-bar
131
+ {
132
+ sel: 'scb-app-bar',
133
+ from: 'title-changed',
134
+ to: 'titlechange',
135
+ prop: 'title',
136
+ },
137
+ {
138
+ sel: 'scb-app-bar',
139
+ from: 'position-changed',
140
+ to: 'positionchange',
141
+ prop: 'position',
142
+ },
143
+ { sel: 'scb-app-bar', from: 'type-changed', to: 'typechange' },
144
+ {
145
+ sel: 'scb-app-bar',
146
+ from: 'search-supporting-text-changed',
147
+ to: 'searchsupportingtextchange',
148
+ },
149
+
150
+ // scb-breadcrumb
151
+ {
152
+ sel: 'scb-breadcrumb',
153
+ from: 'show-all-changed',
154
+ to: 'showallchange',
155
+ prop: 'showAll',
156
+ },
157
+ {
158
+ sel: 'scb-breadcrumb-item',
159
+ from: 'current-changed',
160
+ to: 'currentchange',
161
+ prop: 'isCurrent',
162
+ },
163
+
164
+ // scb-button (click mappas inte här, Blazor kan använda @onclick direkt)
165
+ {
166
+ sel: 'scb-button',
167
+ from: 'variant-changed',
168
+ to: 'variantchange',
169
+ prop: 'variant',
170
+ },
171
+ {
172
+ sel: 'scb-button',
173
+ from: 'label-changed',
174
+ to: 'labelchange',
175
+ prop: 'label',
176
+ },
177
+ {
178
+ sel: 'scb-button',
179
+ from: 'disabled-changed',
180
+ to: 'disabledchange',
181
+ prop: 'disabled',
182
+ },
183
+
184
+ // scb-calendar-card
185
+ {
186
+ sel: 'scb-calendar-card',
187
+ from: 'title-changed',
188
+ to: 'titlechange',
189
+ prop: 'title',
190
+ },
191
+ {
192
+ sel: 'scb-calendar-card',
193
+ from: 'subtitle-changed',
194
+ to: 'subtitlechange',
195
+ prop: 'subtitle',
196
+ },
197
+ {
198
+ sel: 'scb-calendar-card',
199
+ from: 'show-media-changed',
200
+ to: 'showmediachange',
201
+ prop: 'showMedia',
202
+ },
203
+ {
204
+ sel: 'scb-calendar-card',
205
+ from: 'media-width-changed',
206
+ to: 'mediawidthchange',
207
+ prop: 'mediaWidth',
208
+ },
209
+ {
210
+ sel: 'scb-calendar-card',
211
+ from: 'media-height-changed',
212
+ to: 'mediaheightchange',
213
+ prop: 'mediaHeight',
214
+ },
215
+
216
+ // scb-card
217
+ {
218
+ sel: 'scb-card',
219
+ from: 'variant-changed',
220
+ to: 'variantchange',
221
+ prop: 'variant',
222
+ },
223
+ {
224
+ sel: 'scb-card',
225
+ from: 'direction-changed',
226
+ to: 'directionchange',
227
+ prop: 'direction',
228
+ },
229
+ {
230
+ sel: 'scb-card',
231
+ from: 'title-changed',
232
+ to: 'titlechange',
233
+ prop: 'title',
234
+ },
235
+ {
236
+ sel: 'scb-card',
237
+ from: 'subtitle-changed',
238
+ to: 'subtitlechange',
239
+ prop: 'subtitle',
240
+ },
241
+ {
242
+ sel: 'scb-card',
243
+ from: 'show-media-changed',
244
+ to: 'showmediachange',
245
+ prop: 'showMedia',
246
+ },
247
+ {
248
+ sel: 'scb-card',
249
+ from: 'media-width-changed',
250
+ to: 'mediawidthchange',
251
+ prop: 'mediaWidth',
252
+ },
253
+ {
254
+ sel: 'scb-card',
255
+ from: 'media-height-changed',
256
+ to: 'mediaheightchange',
257
+ prop: 'mediaHeight',
258
+ },
259
+
260
+ // scb-chip
261
+ { sel: 'scb-chip', from: 'remove', to: 'remove' },
262
+ {
263
+ sel: 'scb-chip',
264
+ from: 'selected-changed',
265
+ to: 'selectedchange',
266
+ prop: 'selected',
267
+ },
268
+ {
269
+ sel: 'scb-chip',
270
+ from: 'variant-changed',
271
+ to: 'variantchange',
272
+ prop: 'variant',
273
+ },
274
+ {
275
+ sel: 'scb-chip',
276
+ from: 'label-changed',
277
+ to: 'labelchange',
278
+ prop: 'label',
279
+ },
280
+ {
281
+ sel: 'scb-chip',
282
+ from: 'disabled-changed',
283
+ to: 'disabledchange',
284
+ prop: 'disabled',
285
+ },
286
+ {
287
+ sel: 'scb-chip',
288
+ from: 'elevated-changed',
289
+ to: 'elevatedchange',
290
+ prop: 'elevated',
291
+ },
292
+ {
293
+ sel: 'scb-chip',
294
+ from: 'icon-changed',
295
+ to: 'iconchange',
296
+ prop: 'icon',
297
+ },
298
+
299
+ // scb-checkbox
300
+ {
301
+ sel: 'scb-checkbox',
302
+ from: 'checked-changed',
303
+ to: 'checkedchange',
304
+ prop: 'checked',
305
+ },
306
+ {
307
+ sel: 'scb-checkbox',
308
+ from: 'disabled-changed',
309
+ to: 'disabledchange',
310
+ prop: 'disabled',
311
+ },
312
+ {
313
+ sel: 'scb-checkbox',
314
+ from: 'indeterminate-changed',
315
+ to: 'indeterminatechange',
316
+ prop: 'indeterminate',
317
+ },
318
+
319
+ // scb-checkbox-group
320
+ {
321
+ sel: 'scb-checkbox-group',
322
+ from: 'orientation-changed',
323
+ to: 'orientationchange',
324
+ prop: 'orientation',
325
+ },
326
+ {
327
+ sel: 'scb-checkbox-group',
328
+ from: 'disabled-changed',
329
+ to: 'disabledchange',
330
+ prop: 'disabled',
331
+ },
332
+ {
333
+ sel: 'scb-checkbox-group',
334
+ from: 'spacing-changed',
335
+ to: 'spacingchange',
336
+ prop: 'spacing',
337
+ },
338
+
339
+ // scb-dialog (actions mappas rakt vidare)
340
+ { sel: 'scb-dialog', from: 'ok', to: 'ok' },
341
+ { sel: 'scb-dialog', from: 'cancel', to: 'cancel' },
342
+ { sel: 'scb-dialog', from: 'confirm', to: 'confirm' },
343
+ { sel: 'scb-dialog', from: 'deny', to: 'deny' },
344
+ { sel: 'scb-dialog', from: 'reset', to: 'reset' },
345
+ { sel: 'scb-dialog', from: 'submit', to: 'submit' },
346
+ { sel: 'scb-dialog', from: 'esc', to: 'esc' },
347
+ { sel: 'scb-dialog', from: 'scrim', to: 'scrim' },
348
+
349
+ // scb-drawer
350
+ {
351
+ sel: 'scb-drawer',
352
+ from: 'scb-drawer-opened',
353
+ to: 'openchange',
354
+ prop: 'open',
355
+ },
356
+ {
357
+ sel: 'scb-drawer',
358
+ from: 'scb-drawer-closed',
359
+ to: 'openchange',
360
+ prop: 'open',
361
+ },
362
+
363
+ // scb-drawer-item
364
+ {
365
+ sel: 'scb-drawer-item',
366
+ from: 'scb-drawer-expand',
367
+ to: 'expandedchange',
368
+ },
369
+ { sel: 'scb-drawer-item', from: 'scb-drawer-select', to: 'select' },
370
+
371
+ // scb-fact-card
372
+ {
373
+ sel: 'scb-fact-card',
374
+ from: 'open',
375
+ to: 'openchange',
376
+ prop: 'open',
377
+ },
378
+ {
379
+ sel: 'scb-fact-card',
380
+ from: 'close',
381
+ to: 'openchange',
382
+ prop: 'open',
383
+ },
384
+
385
+ // scb-fact-card-content
386
+ {
387
+ sel: 'scb-fact-card-content',
388
+ from: 'label-changed',
389
+ to: 'labelchange',
390
+ prop: 'label',
391
+ },
392
+ {
393
+ sel: 'scb-fact-card-content',
394
+ from: 'sub-label-changed',
395
+ to: 'sublabelchange',
396
+ prop: 'subLabel',
397
+ },
398
+ {
399
+ sel: 'scb-fact-card-content',
400
+ from: 'supporting-text-changed',
401
+ to: 'supportingtextchange',
402
+ prop: 'supportingText',
403
+ },
404
+
405
+ // scb-horizontal-scroller
406
+ {
407
+ sel: 'scb-horizontal-scroller',
408
+ from: 'scb-scroll',
409
+ to: 'scroll',
410
+ prop: 'scrollLeft',
411
+ },
412
+ {
413
+ sel: 'scb-horizontal-scroller',
414
+ from: 'scb-scroll-start',
415
+ to: 'scrollstart',
416
+ prop: 'scrollLeft',
417
+ },
418
+ {
419
+ sel: 'scb-horizontal-scroller',
420
+ from: 'scb-scroll-end',
421
+ to: 'scrollend',
422
+ prop: 'scrollLeft',
423
+ },
424
+ {
425
+ sel: 'scb-horizontal-scroller',
426
+ from: 'scb-scroll-right',
427
+ to: 'scrollright',
428
+ prop: 'scrollLeft',
429
+ },
430
+ {
431
+ sel: 'scb-horizontal-scroller',
432
+ from: 'scb-scroll-left',
433
+ to: 'scrollleft',
434
+ prop: 'scrollLeft',
435
+ },
436
+
437
+ // scb-icon-button
438
+ {
439
+ sel: 'scb-icon-button',
440
+ from: 'change',
441
+ to: 'selectedchange',
442
+ prop: 'selected',
443
+ },
444
+ {
445
+ sel: 'scb-icon-button',
446
+ from: 'toggle-changed',
447
+ to: 'togglechange',
448
+ prop: 'toggle',
449
+ },
450
+ {
451
+ sel: 'scb-icon-button',
452
+ from: 'icon-changed',
453
+ to: 'iconchange',
454
+ prop: 'icon',
455
+ },
456
+ {
457
+ sel: 'scb-icon-button',
458
+ from: 'disabled-changed',
459
+ to: 'disabledchange',
460
+ prop: 'disabled',
461
+ },
462
+ {
463
+ sel: 'scb-icon-button',
464
+ from: 'tooltip-changed',
465
+ to: 'tooltipchange',
466
+ prop: 'tooltip',
467
+ },
468
+
469
+ // scb-menu
470
+ { sel: 'scb-menu', from: 'open', to: 'openchange', prop: 'open' },
471
+ { sel: 'scb-menu', from: 'close', to: 'openchange', prop: 'open' },
472
+ { sel: 'scb-menu', from: 'toggle', to: 'openchange', prop: 'open' },
473
+
474
+ // scb-search (fristående search-komponent, inte headern)
475
+ {
476
+ sel: 'scb-search',
477
+ from: 'scb-search-input',
478
+ to: 'valuechange',
479
+ prop: 'value',
480
+ },
481
+ {
482
+ sel: 'scb-search',
483
+ from: 'scb-search-submit',
484
+ to: 'search',
485
+ prop: 'value',
486
+ },
487
+ {
488
+ sel: 'scb-search',
489
+ from: 'scb-search-clear',
490
+ to: 'valuechange',
491
+ prop: 'value',
492
+ },
493
+
494
+ // scb-radio-group
495
+ { sel: 'scb-radio-group', from: 'scb-change', to: 'change' },
496
+
497
+ // scb-segmented-button
498
+ {
499
+ sel: 'scb-segmented-button',
500
+ from: 'change',
501
+ to: 'valuechange',
502
+ prop: 'value',
503
+ },
504
+ {
505
+ sel: 'scb-segmented-button',
506
+ from: 'change',
507
+ to: 'valueschange',
508
+ prop: 'values',
509
+ },
510
+
511
+ // scb-snackbar, hanterar öppna/stäng via egna events
512
+ {
513
+ sel: 'scb-snackbar',
514
+ from: 'snackbar-open',
515
+ to: 'openchange',
516
+ mirror: (el) => {
517
+ try {
518
+ el.open = true;
519
+ } catch (_) {}
520
+ el.setAttribute('open', '');
521
+ },
522
+ },
523
+ {
524
+ sel: 'scb-snackbar',
525
+ from: 'snackbar-close',
526
+ to: 'openchange',
527
+ mirror: (el) => {
528
+ try {
529
+ el.open = false;
530
+ } catch (_) {}
531
+ el.removeAttribute('open');
532
+ },
533
+ },
534
+ { sel: 'scb-snackbar', from: 'snackbar-action', to: 'action' },
535
+
536
+ // scb-notification-card, liknande hantering som snackbar
537
+ {
538
+ sel: 'scb-notification-card',
539
+ from: 'notification-open',
540
+ to: 'openchange',
541
+ mirror: (el) => {
542
+ try {
543
+ el.open = true;
544
+ } catch (_) {}
545
+ el.setAttribute('open', '');
546
+ },
547
+ },
548
+ {
549
+ sel: 'scb-notification-card',
550
+ from: 'notification-close',
551
+ to: 'openchange',
552
+ mirror: (el) => {
553
+ try {
554
+ el.open = false;
555
+ } catch (_) {}
556
+ el.removeAttribute('open');
557
+ },
558
+ },
559
+ {
560
+ sel: 'scb-notification-card',
561
+ from: 'close',
562
+ to: 'openchange',
563
+ mirror: (el) => {
564
+ try {
565
+ el.open = false;
566
+ } catch (_) {}
567
+ el.removeAttribute('open');
568
+ },
569
+ },
570
+
571
+ // scb-switch
572
+ {
573
+ sel: 'scb-switch',
574
+ from: 'change',
575
+ to: 'selectedchange',
576
+ prop: 'selected',
577
+ },
578
+
579
+ // scb-tabs
580
+ {
581
+ sel: 'scb-tabs',
582
+ from: 'change',
583
+ to: 'tabschange',
584
+ prop: 'activeTabIndex',
585
+ },
586
+
587
+ // scb-textfield
588
+ {
589
+ sel: 'scb-textfield',
590
+ from: 'onValueChanged',
591
+ to: 'valuechange',
592
+ prop: 'value',
593
+ },
594
+ {
595
+ sel: 'scb-textfield',
596
+ from: 'input',
597
+ to: 'input',
598
+ prop: 'value',
599
+ },
600
+ {
601
+ sel: 'scb-textfield',
602
+ from: 'change',
603
+ to: 'change',
604
+ prop: 'value',
605
+ },
606
+ { sel: 'scb-textfield', from: 'select', to: 'select' },
607
+
608
+ // scb-toc-item
609
+ {
610
+ sel: 'scb-toc-item',
611
+ from: 'expanded-changed',
612
+ to: 'expandedchange',
613
+ prop: 'expanded',
614
+ },
615
+
616
+ // scb-pagination
617
+ { sel: 'scb-pagination', from: 'page-change', to: 'pagechange' },
618
+
619
+ // scb-stepper (stegbyte i steppern)
620
+ {
621
+ sel: 'scb-stepper',
622
+ from: 'scb-step-change',
623
+ to: 'stepchange',
624
+ },
625
+ ];
626
+
627
+ /* Indexering av regler per source event för snabbare lookup vid dispatch. */
628
+ const bySource = new Map();
629
+ for (const r of RULES) {
630
+ if (!bySource.has(r.from)) bySource.set(r.from, []);
631
+ bySource.get(r.from).push(r);
632
+ }
633
+
634
+ /* Registrerar en global listener per käll-event.
635
+ Varje listener hittar relevant komponent och triggar rätt normaliserat event. */
636
+ bySource.forEach((rules, fromEvent) => {
637
+ document.addEventListener(
638
+ fromEvent,
639
+ (ev) => {
640
+ for (const rule of rules) {
641
+ const el = closestInPath(rule.sel, ev);
642
+ if (!el) continue;
643
+
644
+ // Uppdatera elementets state om en mirror function finns
645
+ if (rule.mirror) {
646
+ try {
647
+ rule.mirror(el, ev);
648
+ } catch (_) {}
649
+ } else {
650
+ const detail = ev.detail;
651
+ if (
652
+ rule.prop &&
653
+ detail &&
654
+ Object.prototype.hasOwnProperty.call(detail, rule.prop)
655
+ ) {
656
+ try {
657
+ el[rule.prop] = detail[rule.prop];
658
+ } catch (_) {}
659
+ }
660
+ }
661
+
662
+ // Skicka endast vidare om namn faktiskt ändras
663
+ if (rule.from !== rule.to) {
664
+ emit(el, rule.to, ev);
665
+ }
666
+ }
667
+ },
668
+ { capture: true },
669
+ );
670
+ });
671
+
672
+ /* MutationObserver som lyssnar på attributförändringar
673
+ och skickar motsvarande *change-event när något viktigt ändras. */
674
+ const ATTR_RULES = [
675
+ { sel: 'scb-drawer', attr: 'open', to: 'openchange' },
676
+ { sel: 'scb-fact-card', attr: 'open', to: 'openchange' },
677
+ // scb-accordion-item: emit openchange när attributet open ändras
678
+ { sel: 'scb-accordion-item', attr: 'open', to: 'openchange' },
679
+ { sel: 'scb-icon-button', attr: 'selected', to: 'selectedchange' },
680
+ { sel: 'scb-checkbox', attr: 'checked', to: 'checkedchange' },
681
+ { sel: 'scb-menu', attr: 'open', to: 'openchange' },
682
+ { sel: 'scb-sub-menu', attr: 'open', to: 'openchange' },
683
+ { sel: 'scb-chip', attr: 'selected', to: 'selectedchange' },
684
+ { sel: 'scb-notification-card', attr: 'open', to: 'openchange' },
685
+
686
+ // scb-breadcrumb attribut som ska trigga state-refresh
687
+ { sel: 'scb-breadcrumb', attr: 'show-all', to: 'showallchange' },
688
+ { sel: 'scb-breadcrumb-item', attr: 'is-current', to: 'currentchange' },
689
+ { sel: 'scb-breadcrumb-item', attr: 'current', to: 'currentchange' },
690
+
691
+ // scb-calendar-card attribut som ska trigga state-refresh
692
+ { sel: 'scb-calendar-card', attr: 'title', to: 'titlechange' },
693
+ { sel: 'scb-calendar-card', attr: 'subtitle', to: 'subtitlechange' },
694
+ {
695
+ sel: 'scb-calendar-card',
696
+ attr: 'supporting-text',
697
+ to: 'supportingtextchange',
698
+ },
699
+ { sel: 'scb-calendar-card', attr: 'variant', to: 'variantchange' },
700
+ { sel: 'scb-calendar-card', attr: 'show-media', to: 'showmediachange' },
701
+
702
+ // scb-keyfigure-card attribut som ska trigga state-refresh
703
+ { sel: 'scb-keyfigure-card', attr: 'keyfigure', to: 'keyfigurechange' },
704
+ { sel: 'scb-keyfigure-card', attr: 'subtitle', to: 'subtitlechange' },
705
+ {
706
+ sel: 'scb-keyfigure-card',
707
+ attr: 'supporting-text',
708
+ to: 'supportingtextchange',
709
+ },
710
+ {
711
+ sel: 'scb-keyfigure-card',
712
+ attr: 'card-href',
713
+ to: 'cardhrefchange',
714
+ },
715
+ { sel: 'scb-keyfigure-card', attr: 'unit', to: 'unitchange' },
716
+
717
+ // scb-radio-button
718
+ { sel: 'scb-radio-button', attr: 'checked', to: 'checkedchange' },
719
+ { sel: 'scb-radio-button', attr: 'disabled', to: 'disabledchange' },
720
+ {
721
+ sel: 'scb-radio-button',
722
+ attr: 'aria-disabled',
723
+ to: 'ariadisabledchange',
724
+ },
725
+
726
+ // scb-radio-group
727
+ { sel: 'scb-radio-group', attr: 'disabled', to: 'disabledchange' },
728
+ {
729
+ sel: 'scb-radio-group',
730
+ attr: 'orientation',
731
+ to: 'orientationchange',
732
+ },
733
+ { sel: 'scb-radio-group', attr: 'spacing', to: 'spacingchange' },
734
+ { sel: 'scb-radio-group', attr: 'aria-label', to: 'arialabelchange' },
735
+ { sel: 'scb-radio-group', attr: 'name', to: 'namechange' },
736
+ { sel: 'scb-radio-group', attr: 'role', to: 'rolechange' },
737
+ { sel: 'scb-radio-group', attr: 'value', to: 'valuechange' },
738
+ { sel: 'scb-radio-group', attr: 'dir', to: 'dirchange' },
739
+ { sel: 'scb-radio-group', attr: 'style', to: 'stylechange' },
740
+ { sel: 'scb-radio-group', attr: 'class', to: 'classchange' },
741
+ { sel: 'scb-radio-group', attr: 'id', to: 'idchange' },
742
+ { sel: 'scb-radio-group', attr: 'hidden', to: 'hiddenchange' },
743
+ { sel: 'scb-radio-group', attr: 'tabindex', to: 'tabindexchange' },
744
+ { sel: 'scb-radio-group', attr: 'title', to: 'titlechange' },
745
+ { sel: 'scb-radio-group', attr: 'lang', to: 'langchange' },
746
+ { sel: 'scb-radio-group', attr: 'data-*', to: 'datachange' },
747
+ { sel: 'scb-radio-group', attr: 'draggable', to: 'draggablechange' },
748
+ {
749
+ sel: 'scb-radio-group',
750
+ attr: 'spellcheck',
751
+ to: 'spellcheckchange',
752
+ },
753
+ {
754
+ sel: 'scb-radio-group',
755
+ attr: 'contenteditable',
756
+ to: 'contenteditablechange',
757
+ },
758
+
759
+ { sel: 'scb-snackbar', attr: 'open', to: 'openchange' },
760
+
761
+ // scb-status-pill
762
+ { sel: 'scb-status-pill', attr: 'status', to: 'statuschange' },
763
+ { sel: 'scb-status-pill', attr: 'label', to: 'labelchange' },
764
+ { sel: 'scb-status-pill', attr: 'show-icon', to: 'showiconchange' },
765
+
766
+ { sel: 'scb-switch', attr: 'selected', to: 'selectedchange' },
767
+ { sel: 'scb-switch', attr: 'disabled', to: 'disabledchange' },
768
+ { sel: 'scb-switch', attr: 'icons', to: 'iconschange' },
769
+ { sel: 'scb-switch', attr: 'full-width', to: 'fullwidthchange' },
770
+ { sel: 'scb-switch', attr: 'label', to: 'labelchange' },
771
+ { sel: 'scb-switch', attr: 'aria-label', to: 'arialabelchange' },
772
+ { sel: 'scb-tabs', attr: 'aria-label', to: 'arialabelchange' },
773
+ { sel: 'scb-primary-tab', attr: 'icon', to: 'iconchange' },
774
+ { sel: 'scb-primary-tab', attr: 'icon-only', to: 'icononlychange' },
775
+ { sel: 'scb-primary-tab', attr: 'inline-icon', to: 'inlineiconchange' },
776
+ { sel: 'scb-primary-tab', attr: 'aria-label', to: 'arialabelchange' },
777
+ { sel: 'scb-primary-tab', attr: 'active', to: 'activechange' },
778
+ { sel: 'scb-secondary-tab', attr: 'icon', to: 'iconchange' },
779
+ { sel: 'scb-secondary-tab', attr: 'icon-only', to: 'icononlychange' },
780
+ { sel: 'scb-secondary-tab', attr: 'aria-label', to: 'arialabelchange' },
781
+ { sel: 'scb-secondary-tab', attr: 'active', to: 'activechange' },
782
+ { sel: 'scb-textfield', attr: 'value', to: 'valuechange' },
783
+ { sel: 'scb-textfield', attr: 'label', to: 'labelchange' },
784
+ {
785
+ sel: 'scb-textfield',
786
+ attr: 'supporting-text',
787
+ to: 'supportingtextchange',
788
+ },
789
+ { sel: 'scb-textfield', attr: 'error-text', to: 'errortextchange' },
790
+ { sel: 'scb-textfield', attr: 'leading-icon', to: 'leadingiconchange' },
791
+ { sel: 'scb-textfield', attr: 'type', to: 'typechange' },
792
+ { sel: 'scb-textfield', attr: 'name', to: 'namechange' },
793
+ { sel: 'scb-textfield', attr: 'pattern', to: 'patternchange' },
794
+ { sel: 'scb-textfield', attr: 'disabled', to: 'disabledchange' },
795
+ { sel: 'scb-textfield', attr: 'required', to: 'requiredchange' },
796
+ { sel: 'scb-textfield', attr: 'error', to: 'errorchange' },
797
+ { sel: 'scb-textfield', attr: 'aria-label', to: 'arialabelchange' },
798
+
799
+ { sel: 'scb-toc', attr: 'detached', to: 'detachedchange' },
800
+ { sel: 'scb-toc-item', attr: 'expanded', to: 'expandedchange' },
801
+ { sel: 'scb-toc-item', attr: 'label', to: 'labelchange' },
802
+ {
803
+ sel: 'scb-toc-item',
804
+ attr: 'supporting-text',
805
+ to: 'supportingtextchange',
806
+ },
807
+ { sel: 'scb-toc-item', attr: 'divider', to: 'dividerchange' },
808
+ { sel: 'scb-toc-item', attr: 'item-href', to: 'itemhrefchange' },
809
+
810
+ { sel: 'scb-tooltip', attr: 'open', to: 'openchange' },
811
+ { sel: 'scb-tooltip', attr: 'variant', to: 'variantchange' },
812
+ { sel: 'scb-tooltip', attr: 'position', to: 'positionchange' },
813
+ { sel: 'scb-tooltip', attr: 'trigger', to: 'triggerchange' },
814
+ { sel: 'scb-tooltip', attr: 'arrow', to: 'arrowchange' },
815
+ { sel: 'scb-tooltip', attr: 'delay', to: 'delaychange' },
816
+ { sel: 'scb-tooltip', attr: 'offset', to: 'offsetchange' },
817
+ { sel: 'scb-tooltip', attr: 'label', to: 'labelchange' },
818
+ {
819
+ sel: 'scb-tooltip',
820
+ attr: 'supporting-text',
821
+ to: 'supportingtextchange',
822
+ },
823
+ { sel: 'scb-dialog', attr: 'open', to: 'openchange' },
824
+ { sel: 'scb-dialog', attr: 'variant', to: 'variantchange' },
825
+ { sel: 'scb-dialog', attr: 'label', to: 'labelchange' },
826
+ { sel: 'scb-dialog', attr: 'icon', to: 'iconchange' },
827
+ {
828
+ sel: 'scb-dialog',
829
+ attr: 'supporting-text',
830
+ to: 'supportingtextchange',
831
+ },
832
+ { sel: 'scb-dialog', attr: 'ok-button', to: 'okbuttonchange' },
833
+ { sel: 'scb-dialog', attr: 'cancel-button', to: 'cancelbuttonchange' },
834
+ { sel: 'scb-dialog', attr: 'delete-button', to: 'deletebuttonchange' },
835
+ { sel: 'scb-dialog', attr: 'scrim-close', to: 'scrimclosechange' },
836
+
837
+ { sel: 'scb-drawer-item', attr: 'selected', to: 'selectedchange' },
838
+ { sel: 'scb-drawer-item', attr: 'label', to: 'labelchange' },
839
+ {
840
+ sel: 'scb-drawer-item',
841
+ attr: 'leading-icon',
842
+ to: 'leadingiconchange',
843
+ },
844
+ { sel: 'scb-drawer-item', attr: 'item-href', to: 'itemhrefchange' },
845
+
846
+ { sel: 'scb-list', attr: 'no-divider', to: 'nodividerchange' },
847
+ { sel: 'scb-list-item', attr: 'type', to: 'typechange' },
848
+ { sel: 'scb-list-item', attr: 'label', to: 'labelchange' },
849
+ {
850
+ sel: 'scb-list-item',
851
+ attr: 'supporting-text',
852
+ to: 'supportingtextchange',
853
+ },
854
+ {
855
+ sel: 'scb-list-item',
856
+ attr: 'trailing-supporting-text',
857
+ to: 'trailingsupportingtextchange',
858
+ },
859
+ { sel: 'scb-list-item', attr: 'headline', to: 'headlinechange' },
860
+ {
861
+ sel: 'scb-list-item',
862
+ attr: 'trailing-headline',
863
+ to: 'trailingheadlinechange',
864
+ },
865
+ { sel: 'scb-list-item', attr: 'href', to: 'hrefchange' },
866
+ { sel: 'scb-list-item', attr: 'current', to: 'currentchange' },
867
+ { sel: 'scb-list-item', attr: 'selected', to: 'selectedchange' },
868
+ { sel: 'scb-list-item', attr: 'checkbox', to: 'checkboxchange' },
869
+ { sel: 'scb-list-item', attr: 'avatar', to: 'avatarchange' },
870
+ { sel: 'scb-list-item', attr: 'overline', to: 'overlinechange' },
871
+ { sel: 'scb-list-item', attr: 'two-line', to: 'twolinechange' },
872
+ { sel: 'scb-list-item', attr: 'three-line', to: 'threelinechange' },
873
+ { sel: 'scb-list-item', attr: 'no-divider', to: 'nodividerchange' },
874
+ {
875
+ sel: 'scb-list-item',
876
+ attr: 'leading-variant',
877
+ to: 'leadingvariantchange',
878
+ },
879
+ {
880
+ sel: 'scb-list-item',
881
+ attr: 'trailing-variant',
882
+ to: 'trailingvariantchange',
883
+ },
884
+ { sel: 'scb-list-item', attr: 'density', to: 'densitychange' },
885
+ {
886
+ sel: 'scb-list-item',
887
+ attr: 'img-href-image',
888
+ to: 'imghrefimagechange',
889
+ },
890
+
891
+ { sel: 'scb-menu-item', attr: 'leading-icon', to: 'leadingiconchange' },
892
+ {
893
+ sel: 'scb-menu-item',
894
+ attr: 'trailing-icon',
895
+ to: 'trailingiconchange',
896
+ },
897
+ { sel: 'scb-menu-item', attr: 'item-href', to: 'itemhrefchange' },
898
+
899
+ {
900
+ sel: 'scb-notification-card',
901
+ attr: 'direction',
902
+ to: 'directionchange',
903
+ },
904
+ {
905
+ sel: 'scb-notification-card',
906
+ attr: 'show-icon',
907
+ to: 'showiconchange',
908
+ },
909
+ {
910
+ sel: 'scb-notification-card',
911
+ attr: 'show-close-button',
912
+ to: 'showclosebuttonchange',
913
+ },
914
+ {
915
+ sel: 'scb-notification-card',
916
+ attr: 'full-height',
917
+ to: 'fullheightchange',
918
+ },
919
+ {
920
+ sel: 'scb-notification-card',
921
+ attr: 'full-width',
922
+ to: 'fullwidthchange',
923
+ },
924
+
925
+ {
926
+ sel: 'scb-progress-indicator',
927
+ attr: 'type',
928
+ to: 'typechange',
929
+ },
930
+ {
931
+ sel: 'scb-progress-indicator',
932
+ attr: 'progress',
933
+ to: 'progresschange',
934
+ },
935
+ {
936
+ sel: 'scb-progress-indicator',
937
+ attr: 'is-static',
938
+ to: 'isstaticchange',
939
+ },
940
+
941
+ // scb-segmented-button: ändring av value-attribut ska trigga valuechange
942
+ { sel: 'scb-segmented-button', attr: 'value', to: 'valuechange' },
943
+
944
+ // scb-search: ändring av value-attribut ska trigga valuechange
945
+ { sel: 'scb-search', attr: 'value', to: 'valuechange' },
946
+
947
+ // scb-pagination: ändring av page-attribut ska trigga pagechange
948
+ { sel: 'scb-pagination', attr: 'page', to: 'pagechange' },
949
+
950
+ // scb-viz: attribut som ska trigga state-refresh
951
+ { sel: 'scb-viz', attr: 'variant', to: 'variantchange' },
952
+ { sel: 'scb-viz', attr: 'title', to: 'titlechange' },
953
+ { sel: 'scb-viz', attr: 'subtitle', to: 'subtitlechange' },
954
+ { sel: 'scb-viz', attr: 'description', to: 'descriptionchange' },
955
+ { sel: 'scb-viz', attr: 'comment', to: 'commentchange' },
956
+ { sel: 'scb-viz', attr: 'source', to: 'sourcechange' },
957
+ { sel: 'scb-viz', attr: 'footnote', to: 'footnotechange' },
958
+ { sel: 'scb-viz', attr: 'lang', to: 'langchange' },
959
+ { sel: 'scb-viz', attr: 'image-href', to: 'imagehrefchange' },
960
+ ];
961
+
962
+ /* Indexering av attributregler per attributnamn så att observern kan köra snabbare lookup. */
963
+ const ATTR_INDEX = new Map();
964
+ for (const r of ATTR_RULES) {
965
+ if (!ATTR_INDEX.has(r.attr)) ATTR_INDEX.set(r.attr, []);
966
+ ATTR_INDEX.get(r.attr).push(r);
967
+ }
968
+
969
+ /* Själva MutationObservern som bevakar attributändringar på alla relevanta element. */
970
+ const observer = new MutationObserver((mutations) => {
971
+ for (const m of mutations) {
972
+ if (m.type !== 'attributes') continue;
973
+ const el = m.target;
974
+ const attr = m.attributeName;
975
+ if (!(el instanceof Element) || !attr) continue;
976
+ const candidates = ATTR_INDEX.get(attr);
977
+ if (!candidates) continue;
978
+ for (const r of candidates) {
979
+ if (el.matches(r.sel)) emit(el, r.to);
980
+ }
981
+ }
982
+ });
983
+
984
+ observer.observe(document.documentElement, {
985
+ subtree: true,
986
+ attributes: true,
987
+ attributeFilter: Array.from(ATTR_INDEX.keys()),
988
+ });
989
+ })();
990
+
991
+ /* Globalt SCBBlazor-objekt med getState som läser av aktuellt state för alla relevanta komponenter på sidan. */
992
+ window.SCBBlazor = window.SCBBlazor || {};
993
+ window.SCBBlazor.getState = function () {
994
+ const header = document.querySelector('scb-header');
995
+ const drawer = document.querySelector('scb-drawer');
996
+ const breadcrumb = document.querySelector('scb-breadcrumb');
997
+ const breadcrumbItems = document.querySelectorAll('scb-breadcrumb-item');
998
+ const accordions = document.querySelectorAll('scb-accordion');
999
+ const checkboxes = document.querySelectorAll('scb-checkbox');
1000
+ const switches = document.querySelectorAll('scb-switch');
1001
+ const appBar = document.querySelector('scb-app-bar');
1002
+ const segmented = document.querySelector('scb-segmented-button');
1003
+ const tabs = document.querySelectorAll('scb-tabs');
1004
+ const calendars = document.querySelectorAll('scb-calendar-card');
1005
+ const cards = document.querySelectorAll('scb-card');
1006
+ const keyfigures = document.querySelectorAll('scb-keyfigure-card');
1007
+ const menu = document.querySelector('scb-menu');
1008
+ const dialog = document.querySelector('scb-dialog');
1009
+ const notifications = document.querySelectorAll('scb-notification-card');
1010
+ const radioGroups = document.querySelectorAll('scb-radio-group');
1011
+ const paginations = document.querySelectorAll('scb-pagination');
1012
+ const snackbars = document.querySelectorAll('scb-snackbar');
1013
+ const statusPills = document.querySelectorAll('scb-status-pill');
1014
+ const stepper = document.querySelector('scb-stepper');
1015
+ const tooltips = document.querySelectorAll('scb-tooltip');
1016
+ const textfields = document.querySelectorAll('scb-textfield');
1017
+ const searches = document.querySelectorAll('scb-search');
1018
+ const tocs = document.querySelectorAll('scb-toc');
1019
+ const vizs = document.querySelectorAll('scb-viz');
1020
+ const lists = document.querySelectorAll('scb-list');
1021
+ const links = document.querySelectorAll('scb-link');
1022
+ const subMenus = document.querySelectorAll('scb-sub-menu');
1023
+
1024
+ // Headerstate hämtas från attributen på första scb-header
1025
+ const headerState = {
1026
+ activeTab: header
1027
+ ? parseInt(header.getAttribute('active-tab') || '0', 10) || 0
1028
+ : 0,
1029
+ drawerOpen: header ? header.hasAttribute('drawer-open') : false,
1030
+ searchText: header ? header.getAttribute('search-text') || '' : '',
1031
+ };
1032
+
1033
+ // Drawerstate hämtas från attributen på första scb-drawer
1034
+ const drawerState = {
1035
+ open: drawer ? drawer.hasAttribute('open') : false,
1036
+ label: drawer ? drawer.getAttribute('label') || '' : '',
1037
+ subLabel: drawer ? drawer.getAttribute('sub-label') || '' : '',
1038
+ };
1039
+
1040
+ // Breadcrumb (första på sidan)
1041
+ const breadcrumbState = {
1042
+ showAll: breadcrumb
1043
+ ? !!(
1044
+ breadcrumb.showAll ?? breadcrumb.hasAttribute('show-all')
1045
+ )
1046
+ : false,
1047
+ };
1048
+
1049
+ const breadcrumbItemsState = Array.from(breadcrumbItems).map((item) => {
1050
+ const anyItem = item;
1051
+ const label = (
1052
+ anyItem.label ?? item.getAttribute('label') ?? ''
1053
+ ).toString();
1054
+
1055
+ const href = (
1056
+ anyItem.href ??
1057
+ anyItem.itemHref ??
1058
+ item.getAttribute('item-href') ??
1059
+ item.getAttribute('href') ??
1060
+ ''
1061
+ ).toString();
1062
+
1063
+ const isCurrent =
1064
+ typeof anyItem.isCurrent === 'boolean'
1065
+ ? anyItem.isCurrent
1066
+ : item.hasAttribute('is-current') || item.hasAttribute('current');
1067
+
1068
+ return {
1069
+ label,
1070
+ href,
1071
+ isCurrent: !!isCurrent,
1072
+ };
1073
+ });
1074
+
1075
+ // Generell modell för alla scb-accordion med sina scb-accordion-item
1076
+ const accordionsState = Array.from(accordions).map((acc) => {
1077
+ const items = acc.querySelectorAll('scb-accordion-item');
1078
+ return {
1079
+ detached: acc.hasAttribute('detached'),
1080
+ items: Array.from(items).map((item) => ({
1081
+ open: item.hasAttribute('open'),
1082
+ title: item.getAttribute('title') || '',
1083
+ overline: item.getAttribute('overline') || '',
1084
+ supportingText: item.getAttribute('supporting-text') || '',
1085
+ })),
1086
+ };
1087
+ });
1088
+
1089
+ const checkboxesState = Array.from(checkboxes).map((cb) => {
1090
+ const anyCb = cb;
1091
+ const checked =
1092
+ typeof anyCb.checked === 'boolean'
1093
+ ? anyCb.checked
1094
+ : cb.hasAttribute('checked');
1095
+ return !!checked;
1096
+ });
1097
+
1098
+
1099
+ // Switchar på sidan hämtas som bool beroende på selected
1100
+ const switchesState = Array.from(switches).map((sw) => {
1101
+ const anySw = sw;
1102
+ const selected =
1103
+ typeof anySw.selected === 'boolean'
1104
+ ? anySw.selected
1105
+ : sw.hasAttribute('selected');
1106
+ return !!selected;
1107
+ });
1108
+
1109
+ // App bar, läser direkt från attributen
1110
+ const appBarState = {
1111
+ title: appBar ? appBar.getAttribute('title') || '' : '',
1112
+ type: appBar ? appBar.getAttribute('type') || '' : '',
1113
+ position: appBar ? appBar.getAttribute('position') || '' : '',
1114
+ searchSupportingText: appBar
1115
+ ? appBar.getAttribute('search-supporting-text') || ''
1116
+ : '',
1117
+ };
1118
+
1119
+ // Första scb-segmented-button på sidan
1120
+ let segValue = '';
1121
+ let segValues = [];
1122
+
1123
+ if (segmented) {
1124
+ const hostValue =
1125
+ segmented.value ?? segmented.getAttribute('value') ?? '';
1126
+ const hostValues = Array.isArray(segmented.values)
1127
+ ? segmented.values
1128
+ : [];
1129
+
1130
+ if (hostValue || (hostValues && hostValues.length)) {
1131
+ const firstHostValue =
1132
+ hostValues && hostValues.length ? hostValues[0] : '';
1133
+ segValue = String(hostValue || firstHostValue);
1134
+ if (hostValues && hostValues.length) {
1135
+ segValues = hostValues.slice();
1136
+ } else if (segValue) {
1137
+ segValues = [segValue];
1138
+ }
1139
+ }
1140
+ }
1141
+
1142
+ const segmentedState = {
1143
+ value: segValue,
1144
+ values: segValues,
1145
+ };
1146
+
1147
+ // Alla scb-tabs med aktiv index per tabbar
1148
+ const tabsState = Array.from(tabs).map((t) => {
1149
+ const anyT = t;
1150
+ let activeIndex = 0;
1151
+
1152
+ if (typeof anyT.activeTabIndex === 'number') {
1153
+ activeIndex = anyT.activeTabIndex;
1154
+ } else {
1155
+ const attrVal = t.getAttribute('active-tab-index');
1156
+ if (attrVal != null) {
1157
+ const parsed = parseInt(attrVal, 10);
1158
+ if (!Number.isNaN(parsed)) {
1159
+ activeIndex = parsed;
1160
+ }
1161
+ }
1162
+ }
1163
+
1164
+ return { activeIndex };
1165
+ });
1166
+
1167
+ // Alla scb-calendar-card på sidan
1168
+ const calendarsState = Array.from(calendars).map((c) => ({
1169
+ title: c.getAttribute('title') || '',
1170
+ subtitle: c.getAttribute('subtitle') || '',
1171
+ supportingText: c.getAttribute('supporting-text') || '',
1172
+ variant: c.getAttribute('variant') || '',
1173
+ showMedia: c.hasAttribute('show-media'),
1174
+ }));
1175
+
1176
+ // Enkelt kalenderläge för den första kalendern
1177
+ const calendarState =
1178
+ calendarsState.length > 0
1179
+ ? calendarsState[0]
1180
+ : {
1181
+ title: '',
1182
+ subtitle: '',
1183
+ supportingText: '',
1184
+ variant: '',
1185
+ showMedia: false,
1186
+ };
1187
+
1188
+ // Alla scb-card på sidan
1189
+ const cardsState = Array.from(cards).map((card) => ({
1190
+ type: card.getAttribute('type') || '',
1191
+ variant: card.getAttribute('variant') || '',
1192
+ direction: card.getAttribute('direction') || '',
1193
+ title: card.getAttribute('title') || '',
1194
+ subtitle: card.getAttribute('subtitle') || '',
1195
+ supportingText: card.getAttribute('supporting-text') || '',
1196
+ cardHref: card.getAttribute('card-href') || '',
1197
+ }));
1198
+
1199
+ // Alla scb-keyfigure-card på sidan
1200
+ const keyfiguresState = Array.from(keyfigures).map((card) => ({
1201
+ keyfigure: card.getAttribute('keyfigure') || '',
1202
+ subtitle: card.getAttribute('subtitle') || '',
1203
+ supportingText: card.getAttribute('supporting-text') || '',
1204
+ cardHref: card.getAttribute('card-href') || '',
1205
+ icon: card.getAttribute('icon') || '',
1206
+ size: card.getAttribute('size') || '',
1207
+ unit: card.getAttribute('unit') || '',
1208
+ }));
1209
+
1210
+ // Meny (första scb-menu)
1211
+ const menuState = {
1212
+ open: menu ? menu.hasAttribute('open') : false,
1213
+ };
1214
+
1215
+ // Alla notification cards på sidan
1216
+ const notificationsState = Array.from(notifications).map((card) => ({
1217
+ open: card.hasAttribute('open'),
1218
+ variant: card.getAttribute('variant') || '',
1219
+ title: card.getAttribute('title') || '',
1220
+ subtitle: card.getAttribute('subtitle') || '',
1221
+ supportingText: card.getAttribute('supporting-text') || '',
1222
+ linkText: card.getAttribute('link-text') || '',
1223
+ linkHref: card.getAttribute('link-href') || '',
1224
+ showIcon: card.hasAttribute('show-icon'),
1225
+ showCloseButton: card.hasAttribute('show-close-button'),
1226
+ fullHeight: card.hasAttribute('full-height'),
1227
+ fullWidth: card.hasAttribute('full-width'),
1228
+ direction: card.getAttribute('direction') || '',
1229
+ }));
1230
+
1231
+ // Alla snackbars, med stöd för både attribut och properties
1232
+ const snackbarsState = Array.from(snackbars).map((sb) => {
1233
+ const anySb = sb;
1234
+ const message =
1235
+ (anySb.message ?? sb.getAttribute('message') ?? '').toString();
1236
+ const actionText =
1237
+ (anySb.actionText ?? sb.getAttribute('action-text') ?? '').toString();
1238
+ const showClose =
1239
+ typeof anySb.showClose === 'boolean'
1240
+ ? anySb.showClose
1241
+ : sb.hasAttribute('show-close');
1242
+ const fixed =
1243
+ typeof anySb.fixed === 'boolean'
1244
+ ? anySb.fixed
1245
+ : sb.hasAttribute('fixed');
1246
+ const fadeout =
1247
+ typeof anySb.fadeout === 'boolean'
1248
+ ? anySb.fadeout
1249
+ : sb.hasAttribute('fadeout');
1250
+ const withLongerAction =
1251
+ typeof anySb.withLongerAction === 'boolean'
1252
+ ? anySb.withLongerAction
1253
+ : sb.hasAttribute('with-longer-action');
1254
+
1255
+ return {
1256
+ open: sb.hasAttribute('open'),
1257
+ message,
1258
+ actionText,
1259
+ showClose,
1260
+ fixed,
1261
+ fadeout,
1262
+ withLongerAction,
1263
+ };
1264
+ });
1265
+
1266
+ // Alla status pills på sidan
1267
+ const statusPillsState = Array.from(statusPills).map((sp) => {
1268
+ const anySp = sp;
1269
+ const status =
1270
+ (anySp.status ?? sp.getAttribute('status') ?? '').toString();
1271
+ const label =
1272
+ (anySp.label ?? sp.getAttribute('label') ?? '').toString();
1273
+ const showIcon =
1274
+ typeof anySp.showIcon === 'boolean'
1275
+ ? anySp.showIcon
1276
+ : sp.hasAttribute('show-icon');
1277
+
1278
+ return {
1279
+ status,
1280
+ label,
1281
+ showIcon,
1282
+ };
1283
+ });
1284
+
1285
+ // Alla tooltips på sidan
1286
+ const tooltipsState = Array.from(tooltips).map((tooltip) => {
1287
+ const anyTooltip = tooltip;
1288
+ const open =
1289
+ typeof anyTooltip.open === 'boolean'
1290
+ ? anyTooltip.open
1291
+ : tooltip.hasAttribute('open');
1292
+
1293
+ const variant = (
1294
+ anyTooltip.variant ??
1295
+ tooltip.getAttribute('variant') ??
1296
+ ''
1297
+ ).toString();
1298
+
1299
+ const position = (
1300
+ anyTooltip.position ??
1301
+ tooltip.getAttribute('position') ??
1302
+ ''
1303
+ ).toString();
1304
+
1305
+ const trigger = (
1306
+ anyTooltip.trigger ??
1307
+ tooltip.getAttribute('trigger') ??
1308
+ ''
1309
+ ).toString();
1310
+
1311
+ let delay = 0;
1312
+ if (typeof anyTooltip.delay === 'number') {
1313
+ delay = anyTooltip.delay;
1314
+ } else {
1315
+ const attrDelay = tooltip.getAttribute('delay');
1316
+ const parsed =
1317
+ attrDelay != null ? Number.parseFloat(attrDelay) : Number.NaN;
1318
+ delay = Number.isNaN(parsed) ? 0 : parsed;
1319
+ }
1320
+
1321
+ let offset = 0;
1322
+ if (typeof anyTooltip.offset === 'number') {
1323
+ offset = anyTooltip.offset;
1324
+ } else {
1325
+ const attrOffset = tooltip.getAttribute('offset');
1326
+ const parsed =
1327
+ attrOffset != null ? Number.parseFloat(attrOffset) : Number.NaN;
1328
+ offset = Number.isNaN(parsed) ? 0 : parsed;
1329
+ }
1330
+
1331
+ const label = (
1332
+ anyTooltip.label ??
1333
+ tooltip.getAttribute('label') ??
1334
+ ''
1335
+ ).toString();
1336
+
1337
+ const supportingText = (
1338
+ anyTooltip.supportingtext ??
1339
+ tooltip.getAttribute('supporting-text') ??
1340
+ ''
1341
+ ).toString();
1342
+
1343
+ return {
1344
+ open: !!open,
1345
+ variant,
1346
+ position,
1347
+ trigger,
1348
+ delay,
1349
+ offset,
1350
+ label,
1351
+ supportingText,
1352
+ };
1353
+ });
1354
+
1355
+ // Alla radio groups med namn och vald radios value
1356
+ const radioGroupsState = Array.from(radioGroups).map((group) => {
1357
+ const radios = group.querySelectorAll('scb-radio-button');
1358
+ let value = '';
1359
+ for (const rb of Array.from(radios)) {
1360
+ if (rb.hasAttribute('checked')) {
1361
+ value = rb.getAttribute('value') || '';
1362
+ break;
1363
+ }
1364
+ }
1365
+ return {
1366
+ name: group.getAttribute('name') || '',
1367
+ value,
1368
+ };
1369
+ });
1370
+
1371
+ // Alla pagination-komponenter med aktuell sida och antal sidor
1372
+ const paginationsState = Array.from(paginations).map((p) => {
1373
+ const el = p;
1374
+ const pageProp =
1375
+ typeof el.page === 'number'
1376
+ ? el.page
1377
+ : parseInt(p.getAttribute('page') || '0', 10) || 0;
1378
+
1379
+ const totalPagesProp =
1380
+ typeof el.totalPages === 'number'
1381
+ ? el.totalPages
1382
+ : parseInt(p.getAttribute('total-pages') || '0', 10) || 0;
1383
+
1384
+ return {
1385
+ page: pageProp,
1386
+ totalPages: totalPagesProp,
1387
+ };
1388
+ });
1389
+
1390
+ // Dialog (första scb-dialog på sidan)
1391
+ const dialogState = {
1392
+ open: dialog ? dialog.hasAttribute('open') : false,
1393
+ variant: dialog ? dialog.getAttribute('variant') || '' : '',
1394
+ label: dialog ? dialog.getAttribute('label') || '' : '',
1395
+ };
1396
+
1397
+ // Stepper (första scb-stepper på sidan) med aktivt steg och antal steg
1398
+ const stepperState = (function () {
1399
+ if (!stepper) {
1400
+ return { activeIndex: 0, totalSteps: 0 };
1401
+ }
1402
+
1403
+ const anyStepper = stepper;
1404
+ const steps = stepper.querySelectorAll('scb-step');
1405
+ const totalSteps = steps.length;
1406
+
1407
+ let activeIndex = 0;
1408
+
1409
+ if (typeof anyStepper.activeIndex === 'number') {
1410
+ activeIndex = anyStepper.activeIndex;
1411
+ } else {
1412
+ const attrVal = stepper.getAttribute('active-index');
1413
+ if (attrVal != null) {
1414
+ const parsed = parseInt(attrVal, 10);
1415
+ if (!Number.isNaN(parsed)) {
1416
+ activeIndex = parsed;
1417
+ }
1418
+ }
1419
+ }
1420
+
1421
+ return { activeIndex, totalSteps };
1422
+ })();
1423
+
1424
+ // Alla textfält på sidan
1425
+ const textfieldsState = Array.from(textfields).map((tf) => {
1426
+ const anyTf = tf;
1427
+ const value = (
1428
+ anyTf.value ??
1429
+ tf.getAttribute('value') ??
1430
+ ''
1431
+ ).toString();
1432
+
1433
+ const label = (
1434
+ anyTf.label ??
1435
+ tf.getAttribute('label') ??
1436
+ ''
1437
+ ).toString();
1438
+
1439
+ const supportingText = (
1440
+ anyTf.supportingText ??
1441
+ tf.getAttribute('supporting-text') ??
1442
+ ''
1443
+ ).toString();
1444
+
1445
+ const error =
1446
+ typeof anyTf.error === 'boolean'
1447
+ ? anyTf.error
1448
+ : tf.hasAttribute('error');
1449
+
1450
+ return {
1451
+ value,
1452
+ label,
1453
+ supportingText,
1454
+ error,
1455
+ };
1456
+ });
1457
+
1458
+ // Alla search-komponenter på sidan
1459
+ const searchesState = Array.from(searches).map((s) => {
1460
+ const anyS = s;
1461
+ const value = (
1462
+ anyS.value ??
1463
+ s.getAttribute('value') ??
1464
+ ''
1465
+ ).toString();
1466
+
1467
+ const supportingText = (
1468
+ anyS.supportingText ??
1469
+ s.getAttribute('supporting-text') ??
1470
+ ''
1471
+ ).toString();
1472
+
1473
+ const fullScreen =
1474
+ typeof anyS.fullScreen === 'boolean'
1475
+ ? anyS.fullScreen
1476
+ : s.hasAttribute('full-screen');
1477
+
1478
+ const size = (
1479
+ anyS.size ??
1480
+ s.getAttribute('size') ??
1481
+ ''
1482
+ ).toString();
1483
+
1484
+ return {
1485
+ value,
1486
+ supportingText,
1487
+ fullScreen,
1488
+ size,
1489
+ };
1490
+ });
1491
+
1492
+ // Alla scb-toc på sidan med sina scb-toc-item
1493
+ const tocsState = Array.from(tocs).map((toc) => {
1494
+ const items = toc.querySelectorAll('scb-toc-item');
1495
+ return {
1496
+ detached: toc.hasAttribute('detached'),
1497
+ items: Array.from(items).map((item) => {
1498
+ const anyItem = item;
1499
+ const expanded =
1500
+ typeof anyItem.expanded === 'boolean'
1501
+ ? anyItem.expanded
1502
+ : false;
1503
+ const label = (
1504
+ anyItem.label ??
1505
+ item.getAttribute('label') ??
1506
+ ''
1507
+ ).toString();
1508
+ const supportingText = (
1509
+ anyItem.supportingText ??
1510
+ item.getAttribute('supporting-text') ??
1511
+ ''
1512
+ ).toString();
1513
+ const itemHref = (
1514
+ anyItem.itemHref ??
1515
+ item.getAttribute('item-href') ??
1516
+ ''
1517
+ ).toString();
1518
+ const divider =
1519
+ typeof anyItem.divider === 'boolean'
1520
+ ? anyItem.divider
1521
+ : item.hasAttribute('divider');
1522
+
1523
+ return {
1524
+ expanded: !!expanded,
1525
+ label,
1526
+ supportingText,
1527
+ itemHref,
1528
+ divider,
1529
+ };
1530
+ }),
1531
+ };
1532
+ });
1533
+
1534
+ // Alla scb-viz på sidan
1535
+ const vizState = Array.from(vizs).map((v) => {
1536
+ const anyV = v;
1537
+ const variant = (
1538
+ anyV.variant ??
1539
+ v.getAttribute('variant') ??
1540
+ ''
1541
+ ).toString();
1542
+ const title = (
1543
+ anyV.title ??
1544
+ v.getAttribute('title') ??
1545
+ ''
1546
+ ).toString();
1547
+ const subtitle = (
1548
+ anyV.subtitle ??
1549
+ v.getAttribute('subtitle') ??
1550
+ ''
1551
+ ).toString();
1552
+ const description = (
1553
+ anyV.description ??
1554
+ v.getAttribute('description') ??
1555
+ ''
1556
+ ).toString();
1557
+ const comment = (
1558
+ anyV.comment ??
1559
+ v.getAttribute('comment') ??
1560
+ ''
1561
+ ).toString();
1562
+ const source = (
1563
+ anyV.source ??
1564
+ v.getAttribute('source') ??
1565
+ ''
1566
+ ).toString();
1567
+ const footnote = (
1568
+ anyV.footnote ??
1569
+ v.getAttribute('footnote') ??
1570
+ ''
1571
+ ).toString();
1572
+ const lang = (
1573
+ anyV.lang ??
1574
+ v.getAttribute('lang') ??
1575
+ ''
1576
+ ).toString();
1577
+ const imageHref = (
1578
+ anyV.imageHref ??
1579
+ v.getAttribute('image-href') ??
1580
+ ''
1581
+ ).toString();
1582
+ const selectedChip = (
1583
+ anyV.selectedChip ??
1584
+ ''
1585
+ ).toString();
1586
+
1587
+ return {
1588
+ variant,
1589
+ title,
1590
+ subtitle,
1591
+ description,
1592
+ comment,
1593
+ source,
1594
+ footnote,
1595
+ lang,
1596
+ imageHref,
1597
+ selectedChip,
1598
+ };
1599
+ });
1600
+
1601
+ // Alla scb-list med sina scb-list-item
1602
+ const listsState = Array.from(lists).map((list) => {
1603
+ const items = list.querySelectorAll('scb-list-item');
1604
+ return {
1605
+ noDivider: list.hasAttribute('no-divider'),
1606
+ items: Array.from(items).map((item) => {
1607
+ const anyItem = item;
1608
+ const type = (
1609
+ anyItem.type ??
1610
+ item.getAttribute('type') ??
1611
+ ''
1612
+ ).toString();
1613
+ const label = (
1614
+ anyItem.label ??
1615
+ item.getAttribute('label') ??
1616
+ ''
1617
+ ).toString();
1618
+ const supportingText = (
1619
+ anyItem.supportingText ??
1620
+ item.getAttribute('supporting-text') ??
1621
+ ''
1622
+ ).toString();
1623
+ const overline = (
1624
+ anyItem.overline ??
1625
+ item.getAttribute('overline') ??
1626
+ ''
1627
+ ).toString();
1628
+ const leading =
1629
+ typeof anyItem.leading === 'boolean'
1630
+ ? anyItem.leading
1631
+ : item.hasAttribute('leading');
1632
+ const leadingVariant = (
1633
+ anyItem.leadingVariant ??
1634
+ item.getAttribute('leading-variant') ??
1635
+ ''
1636
+ ).toString();
1637
+ const leadingIcon = (
1638
+ anyItem.leadingIcon ??
1639
+ item.getAttribute('leading-icon') ??
1640
+ ''
1641
+ ).toString();
1642
+ const trailing =
1643
+ typeof anyItem.trailing === 'boolean'
1644
+ ? anyItem.trailing
1645
+ : item.hasAttribute('trailing');
1646
+ const trailingVariant = (
1647
+ anyItem.trailingVariant ??
1648
+ item.getAttribute('trailing-variant') ??
1649
+ ''
1650
+ ).toString();
1651
+ const trailingIcon = (
1652
+ anyItem.trailingIcon ??
1653
+ item.getAttribute('trailing-icon') ??
1654
+ ''
1655
+ ).toString();
1656
+ const disabled =
1657
+ typeof anyItem.disabled === 'boolean'
1658
+ ? anyItem.disabled
1659
+ : item.hasAttribute('disabled');
1660
+ const href = (
1661
+ anyItem.href ??
1662
+ item.getAttribute('href') ??
1663
+ ''
1664
+ ).toString();
1665
+ const itemHref = (
1666
+ anyItem.itemHref ??
1667
+ item.getAttribute('item-href') ??
1668
+ ''
1669
+ ).toString();
1670
+ const target = (
1671
+ anyItem.target ??
1672
+ item.getAttribute('target') ??
1673
+ ''
1674
+ ).toString();
1675
+ let density = 0;
1676
+ if (typeof anyItem.density === 'number') {
1677
+ density = anyItem.density;
1678
+ } else {
1679
+ const attrDensity = item.getAttribute('density');
1680
+ const parsed =
1681
+ attrDensity != null ? Number.parseInt(attrDensity, 10) : Number.NaN;
1682
+ density = Number.isNaN(parsed) ? 0 : parsed;
1683
+ }
1684
+ const noDivider =
1685
+ typeof anyItem.noDivider === 'boolean'
1686
+ ? anyItem.noDivider
1687
+ : item.hasAttribute('no-divider');
1688
+ const imgHrefImage = (
1689
+ anyItem.imgHrefImage ??
1690
+ item.getAttribute('img-href-image') ??
1691
+ ''
1692
+ ).toString();
1693
+ const avatarLabel = (
1694
+ anyItem.avatarLabel ??
1695
+ item.getAttribute('avatar-label') ??
1696
+ ''
1697
+ ).toString();
1698
+ const avatarAlt = (
1699
+ anyItem.avatarAlt ??
1700
+ item.getAttribute('avatar-alt') ??
1701
+ ''
1702
+ ).toString();
1703
+ const avatarVariant = (
1704
+ anyItem.avatarVariant ??
1705
+ item.getAttribute('avatar-variant') ??
1706
+ ''
1707
+ ).toString();
1708
+ const avatarSrc = (
1709
+ anyItem.avatarSrc ??
1710
+ item.getAttribute('avatar-src') ??
1711
+ ''
1712
+ ).toString();
1713
+
1714
+ return {
1715
+ type,
1716
+ label,
1717
+ supportingText,
1718
+ overline,
1719
+ leading: !!leading,
1720
+ leadingVariant,
1721
+ leadingIcon,
1722
+ trailing: !!trailing,
1723
+ trailingVariant,
1724
+ trailingIcon,
1725
+ disabled: !!disabled,
1726
+ href,
1727
+ itemHref,
1728
+ target,
1729
+ density,
1730
+ noDivider,
1731
+ imgHrefImage,
1732
+ avatarLabel,
1733
+ avatarAlt,
1734
+ avatarVariant,
1735
+ avatarSrc,
1736
+ };
1737
+ }),
1738
+ };
1739
+ });
1740
+
1741
+ // Alla scb-link på sidan
1742
+ const linksState = Array.from(links).map((link) => {
1743
+ const anyLink = link;
1744
+ const href = (
1745
+ anyLink.href ??
1746
+ link.getAttribute('href') ??
1747
+ ''
1748
+ ).toString();
1749
+ const target = (
1750
+ anyLink.target ??
1751
+ link.getAttribute('target') ??
1752
+ ''
1753
+ ).toString();
1754
+ const rel = (
1755
+ anyLink.rel ??
1756
+ link.getAttribute('rel') ??
1757
+ ''
1758
+ ).toString();
1759
+ const download = (
1760
+ anyLink.download ??
1761
+ link.getAttribute('download') ??
1762
+ ''
1763
+ ).toString();
1764
+ const disabled =
1765
+ typeof anyLink.disabled === 'boolean'
1766
+ ? anyLink.disabled
1767
+ : link.hasAttribute('disabled');
1768
+ const ariaLabel = (
1769
+ anyLink.ariaLabel ??
1770
+ link.getAttribute('aria-label') ??
1771
+ ''
1772
+ ).toString();
1773
+ const text = (
1774
+ anyLink.text ??
1775
+ (link.textContent ?? '') ??
1776
+ ''
1777
+ ).toString().trim();
1778
+
1779
+ return {
1780
+ href,
1781
+ target,
1782
+ rel,
1783
+ download,
1784
+ disabled: !!disabled,
1785
+ ariaLabel,
1786
+ text,
1787
+ };
1788
+ });
1789
+
1790
+ // Alla scb-sub-menu på sidan (till SubMenus i C#-modellen)
1791
+ const subMenusState = Array.from(subMenus).map((sm) => {
1792
+ const anySm = sm;
1793
+
1794
+ const open =
1795
+ typeof anySm.open === 'boolean'
1796
+ ? anySm.open
1797
+ : sm.hasAttribute('open');
1798
+
1799
+ const openLeft =
1800
+ typeof anySm.openLeft === 'boolean'
1801
+ ? anySm.openLeft
1802
+ : sm.hasAttribute('open-left');
1803
+
1804
+ return {
1805
+ open: !!open,
1806
+ openLeft: !!openLeft,
1807
+ };
1808
+ });
1809
+
1810
+
1811
+ // Objektet nedan är det som serialiseras till C#-modellen ScbStateDto
1812
+ return {
1813
+ header: headerState,
1814
+ drawer: drawerState,
1815
+ breadcrumb: breadcrumbState,
1816
+ breadcrumbItems: breadcrumbItemsState,
1817
+ accordions: accordionsState,
1818
+ checkboxes: checkboxesState,
1819
+ switches: switchesState,
1820
+ appBar: appBarState,
1821
+ segmented: segmentedState,
1822
+ tabs: tabsState,
1823
+ calendar: calendarState,
1824
+ calendars: calendarsState,
1825
+ cards: cardsState,
1826
+ keyfigures: keyfiguresState,
1827
+ menu: menuState,
1828
+ notifications: notificationsState,
1829
+ snackbars: snackbarsState,
1830
+ statusPills: statusPillsState,
1831
+ tooltips: tooltipsState,
1832
+ dialog: dialogState,
1833
+ radioGroups: radioGroupsState,
1834
+ paginations: paginationsState,
1835
+ stepper: stepperState,
1836
+ textfields: textfieldsState,
1837
+ searches: searchesState,
1838
+ tocs: tocsState,
1839
+ viz: vizState,
1840
+ lists: listsState,
1841
+ links: linksState,
1842
+ subMenus: subMenusState,
1843
+ };
1844
+ };
1845
+
1846
+ /* Registrerar globala listeners för normaliserade events och anropar den Blazor-komponent som skickat in dotNetRef. */
1847
+ window.SCBBlazor.registerScbEventHandlers = function (dotNetRef) {
1848
+ if (!dotNetRef || window.__scbBlazorHandlersRegistered) return;
1849
+ window.__scbBlazorHandlersRegistered = true;
1850
+
1851
+ // Lista över normaliserade events som ska trigga uppdatering av Blazor state
1852
+ const relevantEvents = [
1853
+ 'tabchange',
1854
+ 'draweropen',
1855
+ 'search',
1856
+ 'openchange',
1857
+ 'checkedchange',
1858
+ 'selectedchange',
1859
+ 'tabschange',
1860
+ 'titlechange',
1861
+ 'positionchange',
1862
+ 'typechange',
1863
+ 'searchsupportingtextchange',
1864
+ // segmented button
1865
+ 'valueschange',
1866
+ // calendar-card mm
1867
+ 'subtitlechange',
1868
+ 'supportingtextchange',
1869
+ 'variantchange',
1870
+ 'showmediachange',
1871
+ // keyfigure-card
1872
+ 'keyfigurechange',
1873
+ 'cardhrefchange',
1874
+ 'unitchange',
1875
+ // status-pill
1876
+ 'statuschange',
1877
+ 'labelchange',
1878
+ 'showiconchange',
1879
+ // breadcrumb
1880
+ 'showallchange',
1881
+ 'currentchange',
1882
+ // pagination
1883
+ 'pagechange',
1884
+ // stepper
1885
+ 'stepchange',
1886
+ // dialog extra
1887
+ 'iconchange',
1888
+ 'okbuttonchange',
1889
+ 'cancelbuttonchange',
1890
+ 'deletebuttonchange',
1891
+ 'scrimclosechange',
1892
+ // notification extra
1893
+ 'directionchange',
1894
+ 'fullheightchange',
1895
+ 'fullwidthchange',
1896
+ 'showclosebuttonchange',
1897
+ // toc
1898
+ 'detachedchange',
1899
+ 'expandedchange',
1900
+ 'dividerchange',
1901
+ 'itemhrefchange',
1902
+ // viz
1903
+ 'descriptionchange',
1904
+ 'commentchange',
1905
+ 'sourcechange',
1906
+ 'footnotechange',
1907
+ 'langchange',
1908
+ 'imagehrefchange',
1909
+ // värdeförändringar, t.ex. scb-textfield och scb-search
1910
+ 'valuechange',
1911
+ ];
1912
+
1913
+ relevantEvents.forEach((evtName) => {
1914
+ document.addEventListener(evtName, () => {
1915
+ dotNetRef
1916
+ .invokeMethodAsync('OnScbEvent', evtName)
1917
+ .catch((err) =>
1918
+ console.error('SCBBlazor.OnScbEvent failed', err),
1919
+ );
1920
+ });
1921
+ });
1922
+ };