scb-wc 0.1.1 → 0.1.2

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,2517 @@
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 source 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
+
1096
+ return {
1097
+ checked: !!checked,
1098
+ name: cb.getAttribute('name') || '',
1099
+ value: cb.getAttribute('value') || '',
1100
+ label: cb.getAttribute('label') || '',
1101
+ };
1102
+ });
1103
+
1104
+ // Switchar på sidan hämtas som objekt med selected, name, value och label
1105
+ const switchesState = Array.from(switches).map((sw) => {
1106
+ const anySw = sw;
1107
+ const selected =
1108
+ typeof anySw.selected === 'boolean'
1109
+ ? anySw.selected
1110
+ : sw.hasAttribute('selected');
1111
+
1112
+ return {
1113
+ selected: !!selected,
1114
+ name: sw.getAttribute('name') || '',
1115
+ value: sw.getAttribute('value') || '',
1116
+ label: sw.getAttribute('label') || '',
1117
+ };
1118
+ });
1119
+
1120
+ // App bar, läser direkt från attributen
1121
+ const appBarState = {
1122
+ title: appBar ? appBar.getAttribute('title') || '' : '',
1123
+ type: appBar ? appBar.getAttribute('type') || '' : '',
1124
+ position: appBar ? appBar.getAttribute('position') || '' : '',
1125
+ searchSupportingText: appBar
1126
+ ? appBar.getAttribute('search-supporting-text') || ''
1127
+ : '',
1128
+ };
1129
+
1130
+ // Första scb-segmented-button på sidan
1131
+ let segValue = '';
1132
+ let segValues = [];
1133
+
1134
+ if (segmented) {
1135
+ const hostValue =
1136
+ segmented.value ?? segmented.getAttribute('value') ?? '';
1137
+ const hostValues = Array.isArray(segmented.values)
1138
+ ? segmented.values
1139
+ : [];
1140
+
1141
+ if (hostValue || (hostValues && hostValues.length)) {
1142
+ const firstHostValue =
1143
+ hostValues && hostValues.length ? hostValues[0] : '';
1144
+ segValue = String(hostValue || firstHostValue);
1145
+ if (hostValues && hostValues.length) {
1146
+ segValues = hostValues.slice();
1147
+ } else if (segValue) {
1148
+ segValues = [segValue];
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ const segmentedState = {
1154
+ value: segValue,
1155
+ values: segValues,
1156
+ };
1157
+
1158
+ // Alla scb-tabs med aktiv index per tabbar
1159
+ const tabsState = Array.from(tabs).map((t) => {
1160
+ const anyT = t;
1161
+ let activeIndex = 0;
1162
+
1163
+ if (typeof anyT.activeTabIndex === 'number') {
1164
+ activeIndex = anyT.activeTabIndex;
1165
+ } else {
1166
+ const attrVal = t.getAttribute('active-tab-index');
1167
+ if (attrVal != null) {
1168
+ const parsed = parseInt(attrVal, 10);
1169
+ if (!Number.isNaN(parsed)) {
1170
+ activeIndex = parsed;
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ return { activeIndex };
1176
+ });
1177
+
1178
+ // Alla scb-calendar-card på sidan
1179
+ const calendarsState = Array.from(calendars).map((c) => ({
1180
+ title: c.getAttribute('title') || '',
1181
+ subtitle: c.getAttribute('subtitle') || '',
1182
+ supportingText: c.getAttribute('supporting-text') || '',
1183
+ variant: c.getAttribute('variant') || '',
1184
+ showMedia: c.hasAttribute('show-media'),
1185
+ }));
1186
+
1187
+ // Enkelt kalenderläge för den första kalendern
1188
+ const calendarState =
1189
+ calendarsState.length > 0
1190
+ ? calendarsState[0]
1191
+ : {
1192
+ title: '',
1193
+ subtitle: '',
1194
+ supportingText: '',
1195
+ variant: '',
1196
+ showMedia: false,
1197
+ };
1198
+
1199
+ // Alla scb-card på sidan
1200
+ const cardsState = Array.from(cards).map((card) => ({
1201
+ type: card.getAttribute('type') || '',
1202
+ variant: card.getAttribute('variant') || '',
1203
+ direction: card.getAttribute('direction') || '',
1204
+ title: card.getAttribute('title') || '',
1205
+ subtitle: card.getAttribute('subtitle') || '',
1206
+ supportingText: card.getAttribute('supporting-text') || '',
1207
+ cardHref: card.getAttribute('card-href') || '',
1208
+ }));
1209
+
1210
+ // Alla scb-keyfigure-card på sidan
1211
+ const keyfiguresState = Array.from(keyfigures).map((card) => ({
1212
+ keyfigure: card.getAttribute('keyfigure') || '',
1213
+ subtitle: card.getAttribute('subtitle') || '',
1214
+ supportingText: card.getAttribute('supporting-text') || '',
1215
+ cardHref: card.getAttribute('card-href') || '',
1216
+ icon: card.getAttribute('icon') || '',
1217
+ size: card.getAttribute('size') || '',
1218
+ unit: card.getAttribute('unit') || '',
1219
+ }));
1220
+
1221
+ // Meny (första scb-menu)
1222
+ const menuState = {
1223
+ open: menu ? menu.hasAttribute('open') : false,
1224
+ };
1225
+
1226
+ // Alla notification cards på sidan
1227
+ const notificationsState = Array.from(notifications).map((card) => ({
1228
+ open: card.hasAttribute('open'),
1229
+ variant: card.getAttribute('variant') || '',
1230
+ title: card.getAttribute('title') || '',
1231
+ subtitle: card.getAttribute('subtitle') || '',
1232
+ supportingText: card.getAttribute('supporting-text') || '',
1233
+ linkText: card.getAttribute('link-text') || '',
1234
+ linkHref: card.getAttribute('link-href') || '',
1235
+ showIcon: card.hasAttribute('show-icon'),
1236
+ showCloseButton: card.hasAttribute('show-close-button'),
1237
+ fullHeight: card.hasAttribute('full-height'),
1238
+ fullWidth: card.hasAttribute('full-width'),
1239
+ direction: card.getAttribute('direction') || '',
1240
+ }));
1241
+
1242
+ // Alla snackbars, med stöd för både attribut och properties
1243
+ const snackbarsState = Array.from(snackbars).map((sb) => {
1244
+ const anySb = sb;
1245
+ const message =
1246
+ (anySb.message ?? sb.getAttribute('message') ?? '').toString();
1247
+ const actionText =
1248
+ (anySb.actionText ?? sb.getAttribute('action-text') ?? '').toString();
1249
+ const showClose =
1250
+ typeof anySb.showClose === 'boolean'
1251
+ ? anySb.showClose
1252
+ : sb.hasAttribute('show-close');
1253
+ const fixed =
1254
+ typeof anySb.fixed === 'boolean'
1255
+ ? anySb.fixed
1256
+ : sb.hasAttribute('fixed');
1257
+ const fadeout =
1258
+ typeof anySb.fadeout === 'boolean'
1259
+ ? anySb.fadeout
1260
+ : sb.hasAttribute('fadeout');
1261
+ const withLongerAction =
1262
+ typeof anySb.withLongerAction === 'boolean'
1263
+ ? anySb.withLongerAction
1264
+ : sb.hasAttribute('with-longer-action');
1265
+
1266
+ return {
1267
+ open: sb.hasAttribute('open'),
1268
+ message,
1269
+ actionText,
1270
+ showClose,
1271
+ fixed,
1272
+ fadeout,
1273
+ withLongerAction,
1274
+ };
1275
+ });
1276
+
1277
+ // Alla status pills på sidan
1278
+ const statusPillsState = Array.from(statusPills).map((sp) => {
1279
+ const anySp = sp;
1280
+ const status =
1281
+ (anySp.status ?? sp.getAttribute('status') ?? '').toString();
1282
+ const label =
1283
+ (anySp.label ?? sp.getAttribute('label') ?? '').toString();
1284
+ const showIcon =
1285
+ typeof anySp.showIcon === 'boolean'
1286
+ ? anySp.showIcon
1287
+ : sp.hasAttribute('show-icon');
1288
+
1289
+ return {
1290
+ status,
1291
+ label,
1292
+ showIcon,
1293
+ };
1294
+ });
1295
+
1296
+ // Alla tooltips på sidan
1297
+ const tooltipsState = Array.from(tooltips).map((tooltip) => {
1298
+ const anyTooltip = tooltip;
1299
+ const open =
1300
+ typeof anyTooltip.open === 'boolean'
1301
+ ? anyTooltip.open
1302
+ : tooltip.hasAttribute('open');
1303
+
1304
+ const variant = (
1305
+ anyTooltip.variant ??
1306
+ tooltip.getAttribute('variant') ??
1307
+ ''
1308
+ ).toString();
1309
+
1310
+ const position = (
1311
+ anyTooltip.position ??
1312
+ tooltip.getAttribute('position') ??
1313
+ ''
1314
+ ).toString();
1315
+
1316
+ const trigger = (
1317
+ anyTooltip.trigger ??
1318
+ tooltip.getAttribute('trigger') ??
1319
+ ''
1320
+ ).toString();
1321
+
1322
+ let delay = 0;
1323
+ if (typeof anyTooltip.delay === 'number') {
1324
+ delay = anyTooltip.delay;
1325
+ } else {
1326
+ const attrDelay = tooltip.getAttribute('delay');
1327
+ const parsed =
1328
+ attrDelay != null ? Number.parseFloat(attrDelay) : Number.NaN;
1329
+ delay = Number.isNaN(parsed) ? 0 : parsed;
1330
+ }
1331
+
1332
+ let offset = 0;
1333
+ if (typeof anyTooltip.offset === 'number') {
1334
+ offset = anyTooltip.offset;
1335
+ } else {
1336
+ const attrOffset = tooltip.getAttribute('offset');
1337
+ const parsed =
1338
+ attrOffset != null ? Number.parseFloat(attrOffset) : Number.NaN;
1339
+ offset = Number.isNaN(parsed) ? 0 : parsed;
1340
+ }
1341
+
1342
+ const label = (
1343
+ anyTooltip.label ??
1344
+ tooltip.getAttribute('label') ??
1345
+ ''
1346
+ ).toString();
1347
+
1348
+ const supportingText = (
1349
+ anyTooltip.supportingtext ??
1350
+ tooltip.getAttribute('supporting-text') ??
1351
+ ''
1352
+ ).toString();
1353
+
1354
+ return {
1355
+ open: !!open,
1356
+ variant,
1357
+ position,
1358
+ trigger,
1359
+ delay,
1360
+ offset,
1361
+ label,
1362
+ supportingText,
1363
+ };
1364
+ });
1365
+
1366
+ // Alla radio groups med namn och vald radios value
1367
+ const radioGroupsState = Array.from(radioGroups).map((group) => {
1368
+ const radios = group.querySelectorAll('scb-radio-button');
1369
+ let value = '';
1370
+ for (const rb of Array.from(radios)) {
1371
+ if (rb.hasAttribute('checked')) {
1372
+ value = rb.getAttribute('value') || '';
1373
+ break;
1374
+ }
1375
+ }
1376
+ return {
1377
+ name: group.getAttribute('name') || '',
1378
+ value,
1379
+ };
1380
+ });
1381
+
1382
+ // Alla pagination-komponenter med aktuell sida och antal sidor
1383
+ const paginationsState = Array.from(paginations).map((p) => {
1384
+ const el = p;
1385
+ const pageProp =
1386
+ typeof el.page === 'number'
1387
+ ? el.page
1388
+ : parseInt(p.getAttribute('page') || '0', 10) || 0;
1389
+
1390
+ const totalPagesProp =
1391
+ typeof el.totalPages === 'number'
1392
+ ? el.totalPages
1393
+ : parseInt(p.getAttribute('total-pages') || '0', 10) || 0;
1394
+
1395
+ return {
1396
+ page: pageProp,
1397
+ totalPages: totalPagesProp,
1398
+ };
1399
+ });
1400
+
1401
+ // Dialog (första scb-dialog på sidan)
1402
+ const dialogState = {
1403
+ open: dialog ? dialog.hasAttribute('open') : false,
1404
+ variant: dialog ? dialog.getAttribute('variant') || '' : '',
1405
+ label: dialog ? dialog.getAttribute('label') || '' : '',
1406
+ };
1407
+
1408
+ // Stepper (första scb-stepper på sidan) med aktivt steg och antal steg
1409
+ const stepperState = (function () {
1410
+ if (!stepper) {
1411
+ return { activeIndex: 0, totalSteps: 0 };
1412
+ }
1413
+
1414
+ const anyStepper = stepper;
1415
+ const steps = stepper.querySelectorAll('scb-step');
1416
+ const totalSteps = steps.length;
1417
+
1418
+ let activeIndex = 0;
1419
+
1420
+ if (typeof anyStepper.activeIndex === 'number') {
1421
+ activeIndex = anyStepper.activeIndex;
1422
+ } else {
1423
+ const attrVal = stepper.getAttribute('active-index');
1424
+ if (attrVal != null) {
1425
+ const parsed = parseInt(attrVal, 10);
1426
+ if (!Number.isNaN(parsed)) {
1427
+ activeIndex = parsed;
1428
+ }
1429
+ }
1430
+ }
1431
+
1432
+ return { activeIndex, totalSteps };
1433
+ })();
1434
+
1435
+ // Alla textfält på sidan
1436
+ const textfieldsState = Array.from(textfields).map((tf) => {
1437
+ const anyTf = tf;
1438
+ const value = (
1439
+ anyTf.value ??
1440
+ tf.getAttribute('value') ??
1441
+ ''
1442
+ ).toString();
1443
+
1444
+ const label = (
1445
+ anyTf.label ??
1446
+ tf.getAttribute('label') ??
1447
+ ''
1448
+ ).toString();
1449
+
1450
+ const supportingText = (
1451
+ anyTf.supportingText ??
1452
+ tf.getAttribute('supporting-text') ??
1453
+ ''
1454
+ ).toString();
1455
+
1456
+ const error =
1457
+ typeof anyTf.error === 'boolean'
1458
+ ? anyTf.error
1459
+ : tf.hasAttribute('error');
1460
+
1461
+ const name = (
1462
+ anyTf.name ??
1463
+ tf.getAttribute('name') ??
1464
+ ''
1465
+ ).toString();
1466
+
1467
+ const id = (
1468
+ anyTf.id ??
1469
+ tf.getAttribute('id') ??
1470
+ ''
1471
+ ).toString();
1472
+
1473
+ return {
1474
+ value,
1475
+ label,
1476
+ supportingText,
1477
+ error,
1478
+ name,
1479
+ id,
1480
+ };
1481
+ });
1482
+
1483
+ // Alla search-komponenter på sidan
1484
+ const searchesState = Array.from(searches).map((s) => {
1485
+ const anyS = s;
1486
+ const value = (
1487
+ anyS.value ??
1488
+ s.getAttribute('value') ??
1489
+ ''
1490
+ ).toString();
1491
+
1492
+ const supportingText = (
1493
+ anyS.supportingText ??
1494
+ s.getAttribute('supporting-text') ??
1495
+ ''
1496
+ ).toString();
1497
+
1498
+ const fullScreen =
1499
+ typeof anyS.fullScreen === 'boolean'
1500
+ ? anyS.fullScreen
1501
+ : s.hasAttribute('full-screen');
1502
+
1503
+ const size = (
1504
+ anyS.size ??
1505
+ s.getAttribute('size') ??
1506
+ ''
1507
+ ).toString();
1508
+
1509
+ const name = (
1510
+ anyS.name ??
1511
+ s.getAttribute('name') ??
1512
+ ''
1513
+ ).toString();
1514
+
1515
+ const id = (
1516
+ anyS.id ??
1517
+ s.getAttribute('id') ??
1518
+ ''
1519
+ ).toString();
1520
+
1521
+ return {
1522
+ value,
1523
+ supportingText,
1524
+ fullScreen,
1525
+ size,
1526
+ name,
1527
+ id,
1528
+ };
1529
+ });
1530
+
1531
+ // Alla scb-toc på sidan med sina scb-toc-item
1532
+ const tocsState = Array.from(tocs).map((toc) => {
1533
+ const items = toc.querySelectorAll('scb-toc-item');
1534
+ return {
1535
+ detached: toc.hasAttribute('detached'),
1536
+ items: Array.from(items).map((item) => {
1537
+ const anyItem = item;
1538
+ const expanded =
1539
+ typeof anyItem.expanded === 'boolean'
1540
+ ? anyItem.expanded
1541
+ : false;
1542
+ const label = (
1543
+ anyItem.label ??
1544
+ item.getAttribute('label') ??
1545
+ ''
1546
+ ).toString();
1547
+ const supportingText = (
1548
+ anyItem.supportingText ??
1549
+ item.getAttribute('supporting-text') ??
1550
+ ''
1551
+ ).toString();
1552
+ const itemHref = (
1553
+ anyItem.itemHref ??
1554
+ item.getAttribute('item-href') ??
1555
+ ''
1556
+ ).toString();
1557
+ const divider =
1558
+ typeof anyItem.divider === 'boolean'
1559
+ ? anyItem.divider
1560
+ : item.hasAttribute('divider');
1561
+
1562
+ return {
1563
+ expanded: !!expanded,
1564
+ label,
1565
+ supportingText,
1566
+ itemHref,
1567
+ divider,
1568
+ };
1569
+ }),
1570
+ };
1571
+ });
1572
+
1573
+ // Alla scb-viz på sidan
1574
+ const vizState = Array.from(vizs).map((v) => {
1575
+ const anyV = v;
1576
+ const variant = (
1577
+ anyV.variant ??
1578
+ v.getAttribute('variant') ??
1579
+ ''
1580
+ ).toString();
1581
+ const title = (
1582
+ anyV.title ??
1583
+ v.getAttribute('title') ??
1584
+ ''
1585
+ ).toString();
1586
+ const subtitle = (
1587
+ anyV.subtitle ??
1588
+ v.getAttribute('subtitle') ??
1589
+ ''
1590
+ ).toString();
1591
+ const description = (
1592
+ anyV.description ??
1593
+ v.getAttribute('description') ??
1594
+ ''
1595
+ ).toString();
1596
+ const comment = (
1597
+ anyV.comment ??
1598
+ v.getAttribute('comment') ??
1599
+ ''
1600
+ ).toString();
1601
+ const source = (
1602
+ anyV.source ??
1603
+ v.getAttribute('source') ??
1604
+ ''
1605
+ ).toString();
1606
+ const footnote = (
1607
+ anyV.footnote ??
1608
+ v.getAttribute('footnote') ??
1609
+ ''
1610
+ ).toString();
1611
+ const lang = (
1612
+ anyV.lang ??
1613
+ v.getAttribute('lang') ??
1614
+ ''
1615
+ ).toString();
1616
+ const imageHref = (
1617
+ anyV.imageHref ??
1618
+ v.getAttribute('image-href') ??
1619
+ ''
1620
+ ).toString();
1621
+ const selectedChip = (
1622
+ anyV.selectedChip ??
1623
+ ''
1624
+ ).toString();
1625
+
1626
+ return {
1627
+ variant,
1628
+ title,
1629
+ subtitle,
1630
+ description,
1631
+ comment,
1632
+ source,
1633
+ footnote,
1634
+ lang,
1635
+ imageHref,
1636
+ selectedChip,
1637
+ };
1638
+ });
1639
+
1640
+ // Alla scb-list med sina scb-list-item
1641
+ const listsState = Array.from(lists).map((list) => {
1642
+ const items = list.querySelectorAll('scb-list-item');
1643
+ return {
1644
+ noDivider: list.hasAttribute('no-divider'),
1645
+ items: Array.from(items).map((item) => {
1646
+ const anyItem = item;
1647
+ const type = (
1648
+ anyItem.type ??
1649
+ item.getAttribute('type') ??
1650
+ ''
1651
+ ).toString();
1652
+ const label = (
1653
+ anyItem.label ??
1654
+ item.getAttribute('label') ??
1655
+ ''
1656
+ ).toString();
1657
+ const supportingText = (
1658
+ anyItem.supportingText ??
1659
+ item.getAttribute('supporting-text') ??
1660
+ ''
1661
+ ).toString();
1662
+ const overline = (
1663
+ anyItem.overline ??
1664
+ item.getAttribute('overline') ??
1665
+ ''
1666
+ ).toString();
1667
+ const leading =
1668
+ typeof anyItem.leading === 'boolean'
1669
+ ? anyItem.leading
1670
+ : item.hasAttribute('leading');
1671
+ const leadingVariant = (
1672
+ anyItem.leadingVariant ??
1673
+ item.getAttribute('leading-variant') ??
1674
+ ''
1675
+ ).toString();
1676
+ const leadingIcon = (
1677
+ anyItem.leadingIcon ??
1678
+ item.getAttribute('leading-icon') ??
1679
+ ''
1680
+ ).toString();
1681
+ const trailing =
1682
+ typeof anyItem.trailing === 'boolean'
1683
+ ? anyItem.trailing
1684
+ : item.hasAttribute('trailing');
1685
+ const trailingVariant = (
1686
+ anyItem.trailingVariant ??
1687
+ item.getAttribute('trailing-variant') ??
1688
+ ''
1689
+ ).toString();
1690
+ const trailingIcon = (
1691
+ anyItem.trailingIcon ??
1692
+ item.getAttribute('trailing-icon') ??
1693
+ ''
1694
+ ).toString();
1695
+ const disabled =
1696
+ typeof anyItem.disabled === 'boolean'
1697
+ ? anyItem.disabled
1698
+ : item.hasAttribute('disabled');
1699
+ const href = (
1700
+ anyItem.href ??
1701
+ item.getAttribute('href') ??
1702
+ ''
1703
+ ).toString();
1704
+ const itemHref = (
1705
+ anyItem.itemHref ??
1706
+ item.getAttribute('item-href') ??
1707
+ ''
1708
+ ).toString();
1709
+ const target = (
1710
+ anyItem.target ??
1711
+ item.getAttribute('target') ??
1712
+ ''
1713
+ ).toString();
1714
+ let density = 0;
1715
+ if (typeof anyItem.density === 'number') {
1716
+ density = anyItem.density;
1717
+ } else {
1718
+ const attrDensity = item.getAttribute('density');
1719
+ const parsed =
1720
+ attrDensity != null ? Number.parseInt(attrDensity, 10) : Number.NaN;
1721
+ density = Number.isNaN(parsed) ? 0 : parsed;
1722
+ }
1723
+ const noDivider =
1724
+ typeof anyItem.noDivider === 'boolean'
1725
+ ? anyItem.noDivider
1726
+ : item.hasAttribute('no-divider');
1727
+ const imgHrefImage = (
1728
+ anyItem.imgHrefImage ??
1729
+ item.getAttribute('img-href-image') ??
1730
+ ''
1731
+ ).toString();
1732
+ const avatarLabel = (
1733
+ anyItem.avatarLabel ??
1734
+ item.getAttribute('avatar-label') ??
1735
+ ''
1736
+ ).toString();
1737
+ const avatarAlt = (
1738
+ anyItem.avatarAlt ??
1739
+ item.getAttribute('avatar-alt') ??
1740
+ ''
1741
+ ).toString();
1742
+ const avatarVariant = (
1743
+ anyItem.avatarVariant ??
1744
+ item.getAttribute('avatar-variant') ??
1745
+ ''
1746
+ ).toString();
1747
+ const avatarSrc = (
1748
+ anyItem.avatarSrc ??
1749
+ item.getAttribute('avatar-src') ??
1750
+ ''
1751
+ ).toString();
1752
+
1753
+ return {
1754
+ type,
1755
+ label,
1756
+ supportingText,
1757
+ overline,
1758
+ leading: !!leading,
1759
+ leadingVariant,
1760
+ leadingIcon,
1761
+ trailing: !!trailing,
1762
+ trailingVariant,
1763
+ trailingIcon,
1764
+ disabled: !!disabled,
1765
+ href,
1766
+ itemHref,
1767
+ target,
1768
+ density,
1769
+ noDivider,
1770
+ imgHrefImage,
1771
+ avatarLabel,
1772
+ avatarAlt,
1773
+ avatarVariant,
1774
+ avatarSrc,
1775
+ };
1776
+ }),
1777
+ };
1778
+ });
1779
+
1780
+ // Alla scb-link på sidan
1781
+ const linksState = Array.from(links).map((link) => {
1782
+ const anyLink = link;
1783
+ const href = (
1784
+ anyLink.href ??
1785
+ link.getAttribute('href') ??
1786
+ ''
1787
+ ).toString();
1788
+ const target = (
1789
+ anyLink.target ??
1790
+ link.getAttribute('target') ??
1791
+ ''
1792
+ ).toString();
1793
+ const rel = (
1794
+ anyLink.rel ??
1795
+ link.getAttribute('rel') ??
1796
+ ''
1797
+ ).toString();
1798
+ const download = (
1799
+ anyLink.download ??
1800
+ link.getAttribute('download') ??
1801
+ ''
1802
+ ).toString();
1803
+ const disabled =
1804
+ typeof anyLink.disabled === 'boolean'
1805
+ ? anyLink.disabled
1806
+ : link.hasAttribute('disabled');
1807
+ const ariaLabel = (
1808
+ anyLink.ariaLabel ??
1809
+ link.getAttribute('aria-label') ??
1810
+ ''
1811
+ ).toString();
1812
+ const text = (
1813
+ anyLink.text ??
1814
+ (link.textContent ?? '') ??
1815
+ ''
1816
+ ).toString().trim();
1817
+
1818
+ return {
1819
+ href,
1820
+ target,
1821
+ rel,
1822
+ download,
1823
+ disabled: !!disabled,
1824
+ ariaLabel,
1825
+ text,
1826
+ };
1827
+ });
1828
+
1829
+ // Alla scb-sub-menu på sidan (till SubMenus i C#-modellen)
1830
+ const subMenusState = Array.from(subMenus).map((sm) => {
1831
+ const anySm = sm;
1832
+
1833
+ const open =
1834
+ typeof anySm.open === 'boolean'
1835
+ ? anySm.open
1836
+ : sm.hasAttribute('open');
1837
+
1838
+ const openLeft =
1839
+ typeof anySm.openLeft === 'boolean'
1840
+ ? anySm.openLeft
1841
+ : sm.hasAttribute('open-left');
1842
+
1843
+ return {
1844
+ open: !!open,
1845
+ openLeft: !!openLeft,
1846
+ };
1847
+ });
1848
+
1849
+
1850
+ // Objektet nedan är det som serialiseras till C#-modellen ScbStateDto
1851
+ return {
1852
+ header: headerState,
1853
+ drawer: drawerState,
1854
+ breadcrumb: breadcrumbState,
1855
+ breadcrumbItems: breadcrumbItemsState,
1856
+ accordions: accordionsState,
1857
+ checkboxes: checkboxesState,
1858
+ switches: switchesState,
1859
+ appBar: appBarState,
1860
+ segmented: segmentedState,
1861
+ tabs: tabsState,
1862
+ calendar: calendarState,
1863
+ calendars: calendarsState,
1864
+ cards: cardsState,
1865
+ keyfigures: keyfiguresState,
1866
+ menu: menuState,
1867
+ notifications: notificationsState,
1868
+ snackbars: snackbarsState,
1869
+ statusPills: statusPillsState,
1870
+ tooltips: tooltipsState,
1871
+ dialog: dialogState,
1872
+ radioGroups: radioGroupsState,
1873
+ paginations: paginationsState,
1874
+ stepper: stepperState,
1875
+ textfields: textfieldsState,
1876
+ searches: searchesState,
1877
+ tocs: tocsState,
1878
+ viz: vizState,
1879
+ lists: listsState,
1880
+ links: linksState,
1881
+ subMenus: subMenusState,
1882
+ };
1883
+ };
1884
+
1885
+ /* Registrerar globala listeners för normaliserade events och anropar den Blazor-komponent som skickat in dotNetRef. */
1886
+ window.SCBBlazor.registerScbEventHandlers = function (dotNetRef) {
1887
+ if (!dotNetRef || window.__scbBlazorHandlersRegistered) return;
1888
+ window.__scbBlazorHandlersRegistered = true;
1889
+
1890
+ // Lista över normaliserade events som ska trigga uppdatering av Blazor state
1891
+ const relevantEvents = [
1892
+ 'tabchange',
1893
+ 'draweropen',
1894
+ 'search',
1895
+ 'openchange',
1896
+ 'checkedchange',
1897
+ 'selectedchange',
1898
+ 'tabschange',
1899
+ 'titlechange',
1900
+ 'positionchange',
1901
+ 'typechange',
1902
+ 'searchsupportingtextchange',
1903
+ // segmented button
1904
+ 'valueschange',
1905
+ // calendar-card mm
1906
+ 'subtitlechange',
1907
+ 'supportingtextchange',
1908
+ 'variantchange',
1909
+ 'showmediachange',
1910
+ // keyfigure-card
1911
+ 'keyfigurechange',
1912
+ 'cardhrefchange',
1913
+ 'unitchange',
1914
+ // status-pill
1915
+ 'statuschange',
1916
+ 'labelchange',
1917
+ 'showiconchange',
1918
+ // breadcrumb
1919
+ 'showallchange',
1920
+ 'currentchange',
1921
+ // pagination
1922
+ 'pagechange',
1923
+ // stepper
1924
+ 'stepchange',
1925
+ // dialog extra
1926
+ 'iconchange',
1927
+ 'okbuttonchange',
1928
+ 'cancelbuttonchange',
1929
+ 'deletebuttonchange',
1930
+ 'scrimclosechange',
1931
+ // notification extra
1932
+ 'directionchange',
1933
+ 'fullheightchange',
1934
+ 'fullwidthchange',
1935
+ 'showclosebuttonchange',
1936
+ // toc
1937
+ 'detachedchange',
1938
+ 'expandedchange',
1939
+ 'dividerchange',
1940
+ 'itemhrefchange',
1941
+ // viz
1942
+ 'descriptionchange',
1943
+ 'commentchange',
1944
+ 'sourcechange',
1945
+ 'footnotechange',
1946
+ 'langchange',
1947
+ 'imagehrefchange',
1948
+ // värdeförändringar, t.ex. scb-textfield och scb-search
1949
+ 'valuechange',
1950
+ ];
1951
+
1952
+ relevantEvents.forEach((evtName) => {
1953
+ document.addEventListener(evtName, () => {
1954
+ dotNetRef
1955
+ .invokeMethodAsync('OnScbEvent', evtName)
1956
+ .catch((err) =>
1957
+ console.error('SCBBlazor.OnScbEvent failed', err),
1958
+ );
1959
+ });
1960
+ });
1961
+ };
1962
+
1963
+ /*
1964
+ Styrkommandon från Blazor.
1965
+ Dessa funktioner uppdaterar komponenternas state i DOM och triggar normaliserade events
1966
+ så att SCBBlazor.getState och registerScbEventHandlers kan uppdatera Blazor-modellen.
1967
+ */
1968
+ (function () {
1969
+ 'use strict';
1970
+
1971
+ function getByIndex(selector, index) {
1972
+ const list = document.querySelectorAll(selector);
1973
+ if (!list || list.length === 0) return null;
1974
+ const i = typeof index === 'number' ? index : parseInt(index, 10);
1975
+ if (Number.isNaN(i) || i < 0 || i >= list.length) return null;
1976
+ return list[i];
1977
+ }
1978
+
1979
+ function getById(id, selector) {
1980
+ if (!id) return null;
1981
+ const el = document.getElementById(String(id));
1982
+ if (!el) return null;
1983
+ if (selector && (!(el instanceof Element) || !el.matches(selector))) return null;
1984
+ return el;
1985
+ }
1986
+
1987
+ function toBool(value) {
1988
+ return !!value;
1989
+ }
1990
+
1991
+ function dispatchChange(el, name, detail) {
1992
+ if (!el) return;
1993
+ el.dispatchEvent(
1994
+ new CustomEvent(name, {
1995
+ bubbles: true,
1996
+ composed: true,
1997
+ detail: detail || {},
1998
+ }),
1999
+ );
2000
+ }
2001
+
2002
+ const api = (window.SCBBlazor = window.SCBBlazor || {});
2003
+
2004
+ // Header
2005
+
2006
+ api.setHeaderActiveTab = function (index) {
2007
+ const header = document.querySelector('scb-header');
2008
+ if (!header) return;
2009
+ const i = typeof index === 'number' ? index : parseInt(index, 10) || 0;
2010
+ try {
2011
+ header.activeTab = i;
2012
+ } catch (_) {}
2013
+ header.setAttribute('active-tab', String(i));
2014
+ dispatchChange(header, 'tabchange', { index: i });
2015
+ };
2016
+
2017
+ api.setHeaderDrawerOpen = function (open) {
2018
+ const header = document.querySelector('scb-header');
2019
+ if (!header) return;
2020
+ const isOpen = toBool(open);
2021
+ if (isOpen) header.setAttribute('drawer-open', '');
2022
+ else header.removeAttribute('drawer-open');
2023
+ dispatchChange(header, 'draweropen', { open: isOpen });
2024
+ };
2025
+
2026
+ api.setHeaderSearchText = function (text) {
2027
+ const header = document.querySelector('scb-header');
2028
+ if (!header) return;
2029
+ const value = text == null ? '' : String(text);
2030
+ try {
2031
+ header.searchText = value;
2032
+ } catch (_) {}
2033
+ if (value) header.setAttribute('search-text', value);
2034
+ else header.removeAttribute('search-text');
2035
+ dispatchChange(header, 'search', { value });
2036
+ };
2037
+
2038
+ // Drawer och menu
2039
+
2040
+ api.setDrawerOpen = function (open) {
2041
+ const drawer = document.querySelector('scb-drawer');
2042
+ if (!drawer) return;
2043
+ const isOpen = toBool(open);
2044
+ try {
2045
+ drawer.open = isOpen;
2046
+ } catch (_) {}
2047
+ if (isOpen) drawer.setAttribute('open', '');
2048
+ else drawer.removeAttribute('open');
2049
+ dispatchChange(drawer, 'openchange', { open: isOpen });
2050
+ };
2051
+
2052
+ api.setDrawerText = function (label, subLabel) {
2053
+ const drawer = document.querySelector('scb-drawer');
2054
+ if (!drawer) return;
2055
+ const l = label == null ? '' : String(label);
2056
+ const s = subLabel == null ? '' : String(subLabel);
2057
+ drawer.setAttribute('label', l);
2058
+ drawer.setAttribute('sub-label', s);
2059
+ };
2060
+
2061
+ api.setMenuOpen = function (open) {
2062
+ const menu = document.querySelector('scb-menu');
2063
+ if (!menu) return;
2064
+ const isOpen = toBool(open);
2065
+ try {
2066
+ menu.open = isOpen;
2067
+ } catch (_) {}
2068
+ if (isOpen) menu.setAttribute('open', '');
2069
+ else menu.removeAttribute('open');
2070
+ dispatchChange(menu, 'openchange', { open: isOpen });
2071
+ };
2072
+
2073
+ api.setSubMenuOpen = function (index, open) {
2074
+ const subMenu = getByIndex('scb-sub-menu', index);
2075
+ if (!subMenu) return;
2076
+ const isOpen = toBool(open);
2077
+ try {
2078
+ subMenu.open = isOpen;
2079
+ } catch (_) {}
2080
+ if (isOpen) subMenu.setAttribute('open', '');
2081
+ else subMenu.removeAttribute('open');
2082
+ dispatchChange(subMenu, 'openchange', { open: isOpen });
2083
+ };
2084
+
2085
+ api.setSubMenuOpenById = function (id, open) {
2086
+ const subMenu = getById(id, 'scb-sub-menu');
2087
+ if (!subMenu) return;
2088
+ const isOpen = toBool(open);
2089
+ try {
2090
+ subMenu.open = isOpen;
2091
+ } catch (_) {}
2092
+ if (isOpen) subMenu.setAttribute('open', '');
2093
+ else subMenu.removeAttribute('open');
2094
+ dispatchChange(subMenu, 'openchange', { open: isOpen });
2095
+ };
2096
+
2097
+ // Breadcrumb
2098
+
2099
+ api.setBreadcrumbShowAll = function (showAll) {
2100
+ const breadcrumb = document.querySelector('scb-breadcrumb');
2101
+ if (!breadcrumb) return;
2102
+ const value = toBool(showAll);
2103
+ try {
2104
+ breadcrumb.showAll = value;
2105
+ } catch (_) {}
2106
+ if (value) breadcrumb.setAttribute('show-all', '');
2107
+ else breadcrumb.removeAttribute('show-all');
2108
+ dispatchChange(breadcrumb, 'showallchange', { value });
2109
+ };
2110
+
2111
+ // Accordion
2112
+
2113
+ api.setAccordionItemOpen = function (accordionIndex, itemIndex, open) {
2114
+ const acc = getByIndex('scb-accordion', accordionIndex);
2115
+ if (!acc) return;
2116
+ const items = acc.querySelectorAll('scb-accordion-item');
2117
+ if (!items || items.length === 0) return;
2118
+ const i = typeof itemIndex === 'number' ? itemIndex : parseInt(itemIndex, 10) || 0;
2119
+ if (i < 0 || i >= items.length) return;
2120
+ const item = items[i];
2121
+ const isOpen = toBool(open);
2122
+ try {
2123
+ item.open = isOpen;
2124
+ } catch (_) {}
2125
+ if (isOpen) item.setAttribute('open', '');
2126
+ else item.removeAttribute('open');
2127
+ dispatchChange(item, 'openchange', { open: isOpen });
2128
+ };
2129
+
2130
+ api.setAccordionItemOpenById = function (itemId, open) {
2131
+ const item = getById(itemId, 'scb-accordion-item');
2132
+ if (!item) return;
2133
+ const isOpen = toBool(open);
2134
+ try {
2135
+ item.open = isOpen;
2136
+ } catch (_) {}
2137
+ if (isOpen) item.setAttribute('open', '');
2138
+ else item.removeAttribute('open');
2139
+ dispatchChange(item, 'openchange', { open: isOpen });
2140
+ };
2141
+
2142
+ // Checkbox och switch
2143
+
2144
+ api.setCheckboxChecked = function (index, checked) {
2145
+ const cb = getByIndex('scb-checkbox', index);
2146
+ if (!cb) return;
2147
+ const isChecked = toBool(checked);
2148
+ try {
2149
+ cb.checked = isChecked;
2150
+ } catch (_) {}
2151
+ if (isChecked) cb.setAttribute('checked', '');
2152
+ else cb.removeAttribute('checked');
2153
+ dispatchChange(cb, 'checkedchange', { checked: isChecked });
2154
+ };
2155
+
2156
+ api.setCheckboxCheckedById = function (id, checked) {
2157
+ const cb = getById(id, 'scb-checkbox');
2158
+ if (!cb) return;
2159
+ const isChecked = toBool(checked);
2160
+ try {
2161
+ cb.checked = isChecked;
2162
+ } catch (_) {}
2163
+ if (isChecked) cb.setAttribute('checked', '');
2164
+ else cb.removeAttribute('checked');
2165
+ dispatchChange(cb, 'checkedchange', { checked: isChecked });
2166
+ };
2167
+
2168
+ api.setSwitchSelected = function (index, selected) {
2169
+ const sw = getByIndex('scb-switch', index);
2170
+ if (!sw) return;
2171
+ const isSelected = toBool(selected);
2172
+ try {
2173
+ sw.selected = isSelected;
2174
+ } catch (_) {}
2175
+ if (isSelected) sw.setAttribute('selected', '');
2176
+ else sw.removeAttribute('selected');
2177
+ dispatchChange(sw, 'selectedchange', { selected: isSelected });
2178
+ };
2179
+
2180
+ api.setSwitchSelectedById = function (id, selected) {
2181
+ const sw = getById(id, 'scb-switch');
2182
+ if (!sw) return;
2183
+ const isSelected = toBool(selected);
2184
+ try {
2185
+ sw.selected = isSelected;
2186
+ } catch (_) {}
2187
+ if (isSelected) sw.setAttribute('selected', '');
2188
+ else sw.removeAttribute('selected');
2189
+ dispatchChange(sw, 'selectedchange', { selected: isSelected });
2190
+ };
2191
+
2192
+ // Segmented button
2193
+
2194
+ api.setSegmentedValue = function (value) {
2195
+ const seg = document.querySelector('scb-segmented-button');
2196
+ if (!seg) return;
2197
+ const v = value == null ? '' : String(value);
2198
+ try {
2199
+ seg.value = v;
2200
+ } catch (_) {}
2201
+ if (v) seg.setAttribute('value', v);
2202
+ else seg.removeAttribute('value');
2203
+ dispatchChange(seg, 'valuechange', { value: v });
2204
+ };
2205
+
2206
+ api.setSegmentedValues = function (values) {
2207
+ const seg = document.querySelector('scb-segmented-button');
2208
+ if (!seg) return;
2209
+ const arr = Array.isArray(values) ? values.map((v) => String(v)) : [];
2210
+ try {
2211
+ seg.values = arr;
2212
+ } catch (_) {}
2213
+ if (arr.length > 0) {
2214
+ seg.setAttribute('value', arr[0]);
2215
+ } else {
2216
+ seg.removeAttribute('value');
2217
+ }
2218
+ dispatchChange(seg, 'valueschange', { values: arr });
2219
+ };
2220
+
2221
+ api.setSegmentedValueById = function (id, value) {
2222
+ const seg = getById(id, 'scb-segmented-button');
2223
+ if (!seg) return;
2224
+ const v = value == null ? '' : String(value);
2225
+ try {
2226
+ seg.value = v;
2227
+ } catch (_) {}
2228
+ if (v) seg.setAttribute('value', v);
2229
+ else seg.removeAttribute('value');
2230
+ dispatchChange(seg, 'valuechange', { value: v });
2231
+ };
2232
+
2233
+ api.setSegmentedValuesById = function (id, values) {
2234
+ const seg = getById(id, 'scb-segmented-button');
2235
+ if (!seg) return;
2236
+ const arr = Array.isArray(values) ? values.map((v) => String(v)) : [];
2237
+ try {
2238
+ seg.values = arr;
2239
+ } catch (_) {}
2240
+ if (arr.length > 0) {
2241
+ seg.setAttribute('value', arr[0]);
2242
+ } else {
2243
+ seg.removeAttribute('value');
2244
+ }
2245
+ dispatchChange(seg, 'valueschange', { values: arr });
2246
+ };
2247
+
2248
+ // Tabs
2249
+
2250
+ api.setTabsActiveIndex = function (tabsIndex, activeIndex) {
2251
+ const tabs = getByIndex('scb-tabs', tabsIndex);
2252
+ if (!tabs) return;
2253
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2254
+ const anyTabs = tabs;
2255
+ try {
2256
+ anyTabs.activeTabIndex = i;
2257
+ } catch (_) {}
2258
+ tabs.setAttribute('active-tab-index', String(i));
2259
+ dispatchChange(tabs, 'tabschange', { activeIndex: i });
2260
+ };
2261
+
2262
+ api.setTabsActiveIndexById = function (id, activeIndex) {
2263
+ const tabs = getById(id, 'scb-tabs');
2264
+ if (!tabs) return;
2265
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2266
+ const anyTabs = tabs;
2267
+ try {
2268
+ anyTabs.activeTabIndex = i;
2269
+ } catch (_) {}
2270
+ tabs.setAttribute('active-tab-index', String(i));
2271
+ dispatchChange(tabs, 'tabschange', { activeIndex: i });
2272
+ };
2273
+
2274
+ // Dialog, notification och snackbar
2275
+
2276
+ api.setDialogOpen = function (open) {
2277
+ const dialog = document.querySelector('scb-dialog');
2278
+ if (!dialog) return;
2279
+ const isOpen = toBool(open);
2280
+ try {
2281
+ dialog.open = isOpen;
2282
+ } catch (_) {}
2283
+ if (isOpen) dialog.setAttribute('open', '');
2284
+ else dialog.removeAttribute('open');
2285
+ dispatchChange(dialog, 'openchange', { open: isOpen });
2286
+ };
2287
+
2288
+ api.setNotificationOpen = function (index, open) {
2289
+ const card = getByIndex('scb-notification-card', index);
2290
+ if (!card) return;
2291
+ const isOpen = toBool(open);
2292
+ if (isOpen) card.setAttribute('open', '');
2293
+ else card.removeAttribute('open');
2294
+ dispatchChange(card, 'openchange', { open: isOpen });
2295
+ };
2296
+
2297
+ api.setNotificationOpenById = function (id, open) {
2298
+ const card = getById(id, 'scb-notification-card');
2299
+ if (!card) return;
2300
+ const isOpen = toBool(open);
2301
+ if (isOpen) card.setAttribute('open', '');
2302
+ else card.removeAttribute('open');
2303
+ dispatchChange(card, 'openchange', { open: isOpen });
2304
+ };
2305
+
2306
+ api.setSnackbarOpen = function (index, open) {
2307
+ const sb = getByIndex('scb-snackbar', index);
2308
+ if (!sb) return;
2309
+ const isOpen = toBool(open);
2310
+ if (isOpen) sb.setAttribute('open', '');
2311
+ else sb.removeAttribute('open');
2312
+ dispatchChange(sb, 'openchange', { open: isOpen });
2313
+ };
2314
+
2315
+ api.setSnackbarOpenById = function (id, open) {
2316
+ const sb = getById(id, 'scb-snackbar');
2317
+ if (!sb) return;
2318
+ const isOpen = toBool(open);
2319
+ if (isOpen) sb.setAttribute('open', '');
2320
+ else sb.removeAttribute('open');
2321
+ dispatchChange(sb, 'openchange', { open: isOpen });
2322
+ };
2323
+
2324
+ // Stepper och pagination
2325
+
2326
+ api.setStepperActiveIndex = function (activeIndex) {
2327
+ const stepper = document.querySelector('scb-stepper');
2328
+ if (!stepper) return;
2329
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2330
+ const anyStepper = stepper;
2331
+ try {
2332
+ anyStepper.activeIndex = i;
2333
+ } catch (_) {}
2334
+ stepper.setAttribute('active-index', String(i));
2335
+ dispatchChange(stepper, 'stepchange', { activeIndex: i });
2336
+ };
2337
+
2338
+ api.setStepperActiveIndexById = function (id, activeIndex) {
2339
+ const stepper = getById(id, 'scb-stepper');
2340
+ if (!stepper) return;
2341
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2342
+ const anyStepper = stepper;
2343
+ try {
2344
+ anyStepper.activeIndex = i;
2345
+ } catch (_) {}
2346
+ stepper.setAttribute('active-index', String(i));
2347
+ dispatchChange(stepper, 'stepchange', { activeIndex: i });
2348
+ };
2349
+
2350
+ api.setPaginationPage = function (paginationIndex, page) {
2351
+ const p = getByIndex('scb-pagination', paginationIndex);
2352
+ if (!p) return;
2353
+ const i = typeof page === 'number' ? page : parseInt(page, 10) || 0;
2354
+ const anyP = p;
2355
+ try {
2356
+ anyP.page = i;
2357
+ } catch (_) {}
2358
+ p.setAttribute('page', String(i));
2359
+ dispatchChange(p, 'pagechange', { page: i });
2360
+ };
2361
+
2362
+ api.setPaginationPageById = function (id, page) {
2363
+ const p = getById(id, 'scb-pagination');
2364
+ if (!p) return;
2365
+ const i = typeof page === 'number' ? page : parseInt(page, 10) || 0;
2366
+ const anyP = p;
2367
+ try {
2368
+ anyP.page = i;
2369
+ } catch (_) {}
2370
+ p.setAttribute('page', String(i));
2371
+ dispatchChange(p, 'pagechange', { page: i });
2372
+ };
2373
+
2374
+ // Radio group
2375
+
2376
+ api.setRadioGroupValueByIndex = function (groupIndex, value) {
2377
+ const group = getByIndex('scb-radio-group', groupIndex);
2378
+ if (!group) return;
2379
+ const v = value == null ? '' : String(value);
2380
+ try {
2381
+ group.value = v;
2382
+ } catch (_) {}
2383
+ group.setAttribute('value', v);
2384
+ dispatchChange(group, 'valuechange', { value: v });
2385
+ };
2386
+
2387
+ api.setRadioGroupValueByName = function (name, value) {
2388
+ if (!name) return;
2389
+ const selector = 'scb-radio-group[name="' + String(name) + '"]';
2390
+ const group = document.querySelector(selector);
2391
+ if (!group) return;
2392
+ const v = value == null ? '' : String(value);
2393
+ try {
2394
+ group.value = v;
2395
+ } catch (_) {}
2396
+ group.setAttribute('value', v);
2397
+ dispatchChange(group, 'valuechange', { value: v });
2398
+ };
2399
+
2400
+ api.setRadioGroupValueById = function (id, value) {
2401
+ const group = getById(id, 'scb-radio-group');
2402
+ if (!group) return;
2403
+ const v = value == null ? '' : String(value);
2404
+ try {
2405
+ group.value = v;
2406
+ } catch (_) {}
2407
+ group.setAttribute('value', v);
2408
+ dispatchChange(group, 'valuechange', { value: v });
2409
+ };
2410
+
2411
+ // Textfield och search
2412
+
2413
+ api.setTextfieldValue = function (index, value) {
2414
+ const tf = getByIndex('scb-textfield', index);
2415
+ if (!tf) return;
2416
+ const v = value == null ? '' : String(value);
2417
+ const anyTf = tf;
2418
+ try {
2419
+ anyTf.value = v;
2420
+ } catch (_) {}
2421
+ tf.setAttribute('value', v);
2422
+ dispatchChange(tf, 'valuechange', { value: v });
2423
+ };
2424
+
2425
+ api.setTextfieldValueById = function (id, value) {
2426
+ const tf = getById(id, 'scb-textfield');
2427
+ if (!tf) return;
2428
+ const v = value == null ? '' : String(value);
2429
+ const anyTf = tf;
2430
+ try {
2431
+ anyTf.value = v;
2432
+ } catch (_) {}
2433
+ tf.setAttribute('value', v);
2434
+ dispatchChange(tf, 'valuechange', { value: v });
2435
+ };
2436
+
2437
+ api.setSearchValue = function (index, value) {
2438
+ const s = getByIndex('scb-search', index);
2439
+ if (!s) return;
2440
+ const v = value == null ? '' : String(value);
2441
+ const anyS = s;
2442
+ try {
2443
+ anyS.value = v;
2444
+ } catch (_) {}
2445
+ s.setAttribute('value', v);
2446
+ dispatchChange(s, 'valuechange', { value: v });
2447
+ };
2448
+
2449
+ api.setSearchValueById = function (id, value) {
2450
+ const s = getById(id, 'scb-search');
2451
+ if (!s) return;
2452
+ const v = value == null ? '' : String(value);
2453
+ const anyS = s;
2454
+ try {
2455
+ anyS.value = v;
2456
+ } catch (_) {}
2457
+ s.setAttribute('value', v);
2458
+ dispatchChange(s, 'valuechange', { value: v });
2459
+ };
2460
+
2461
+ // TOC
2462
+
2463
+ api.setTocItemExpanded = function (tocIndex, itemIndex, expanded) {
2464
+ const toc = getByIndex('scb-toc', tocIndex);
2465
+ if (!toc) return;
2466
+ const items = toc.querySelectorAll('scb-toc-item');
2467
+ if (!items || items.length === 0) return;
2468
+ const i = typeof itemIndex === 'number' ? itemIndex : parseInt(itemIndex, 10) || 0;
2469
+ if (i < 0 || i >= items.length) return;
2470
+ const item = items[i];
2471
+ const isExpanded = toBool(expanded);
2472
+ const anyItem = item;
2473
+ try {
2474
+ anyItem.expanded = isExpanded;
2475
+ } catch (_) {}
2476
+ if (isExpanded) item.setAttribute('expanded', '');
2477
+ else item.removeAttribute('expanded');
2478
+ dispatchChange(item, 'expandedchange', { expanded: isExpanded });
2479
+ };
2480
+
2481
+ api.setTocItemExpandedById = function (itemId, expanded) {
2482
+ const item = getById(itemId, 'scb-toc-item');
2483
+ if (!item) return;
2484
+ const isExpanded = toBool(expanded);
2485
+ const anyItem = item;
2486
+ try {
2487
+ anyItem.expanded = isExpanded;
2488
+ } catch (_) {}
2489
+ if (isExpanded) item.setAttribute('expanded', '');
2490
+ else item.removeAttribute('expanded');
2491
+ dispatchChange(item, 'expandedchange', { expanded: isExpanded });
2492
+ };
2493
+
2494
+ // Viz
2495
+
2496
+ api.setVizSelectedChip = function (index, selectedChip) {
2497
+ const viz = getByIndex('scb-viz', index);
2498
+ if (!viz) return;
2499
+ const value = selectedChip == null ? '' : String(selectedChip);
2500
+ const anyViz = viz;
2501
+ try {
2502
+ anyViz.selectedChip = value;
2503
+ } catch (_) {}
2504
+ dispatchChange(viz, 'valuechange', { value });
2505
+ };
2506
+
2507
+ api.setVizSelectedChipById = function (id, selectedChip) {
2508
+ const viz = getById(id, 'scb-viz');
2509
+ if (!viz) return;
2510
+ const value = selectedChip == null ? '' : String(selectedChip);
2511
+ const anyViz = viz;
2512
+ try {
2513
+ anyViz.selectedChip = value;
2514
+ } catch (_) {}
2515
+ dispatchChange(viz, 'valuechange', { value });
2516
+ };
2517
+ })();