scb-wc 0.1.1 → 0.1.3

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,2805 @@
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
+ // Spegla aria-expanded på menyknappen i headerns shadow DOM så att läget blir rätt även vid overlay-stängning
63
+ try {
64
+ const sr = el.shadowRoot;
65
+ if (sr) {
66
+ const btn =
67
+ sr.querySelector('.menu-trigger') ||
68
+ sr.querySelector('[data-drawer-toggle]') ||
69
+ sr.querySelector('[aria-controls]');
70
+ if (btn) btn.setAttribute('aria-expanded', String(!!isOpen));
71
+ }
72
+ } catch (_) {}
73
+ }
74
+
75
+ function mirrorHeaderSearch(el, ev) {
76
+ const v = (ev && ev.detail && (ev.detail.value ?? ev.detail.text)) ?? '';
77
+ try {
78
+ el.searchText = v;
79
+ } catch (_) {}
80
+ if (!v) el.removeAttribute('search-text');
81
+ else el.setAttribute('search-text', String(v));
82
+ }
83
+
84
+ /* Regler som beskriver hur komponenternas egna events ska översättas
85
+ till enklare, Blazor-vänliga events (och ibland speglas till properties). */
86
+ const RULES = [
87
+ // scb-header med mirror fucntions som uppdaterar attribut och state
88
+ {
89
+ sel: 'scb-header',
90
+ from: 'tab-change',
91
+ to: 'tabchange',
92
+ mirror: mirrorHeaderTab,
93
+ },
94
+ {
95
+ sel: 'scb-header',
96
+ from: 'drawer-open',
97
+ to: 'draweropen',
98
+ mirror: (el) => mirrorHeaderDrawer(el, true),
99
+ },
100
+ {
101
+ sel: 'scb-header',
102
+ from: 'drawer-close',
103
+ to: 'draweropen',
104
+ mirror: (el) => mirrorHeaderDrawer(el, false),
105
+ },
106
+ // Speglar headerns drawer-state även när drawern stängs/öppnas via overlay eller ESC
107
+ {
108
+ sel: 'scb-header',
109
+ from: 'scb-drawer-opened',
110
+ to: 'scb-drawer-opened',
111
+ mirror: (el) => mirrorHeaderDrawer(el, true),
112
+ },
113
+ {
114
+ sel: 'scb-header',
115
+ from: 'scb-drawer-closed',
116
+ to: 'scb-drawer-closed',
117
+ mirror: (el) => mirrorHeaderDrawer(el, false),
118
+ },
119
+ { sel: 'scb-header', from: 'drawer-select', to: 'select' },
120
+ { sel: 'scb-header', from: 'menu-click', to: 'menuclick' },
121
+ {
122
+ sel: 'scb-header',
123
+ from: 'search-input',
124
+ to: 'search',
125
+ mirror: mirrorHeaderSearch,
126
+ },
127
+ {
128
+ sel: 'scb-header',
129
+ from: 'search-change',
130
+ to: 'search',
131
+ mirror: mirrorHeaderSearch,
132
+ },
133
+ {
134
+ sel: 'scb-header',
135
+ from: 'search-text-change',
136
+ to: 'search',
137
+ mirror: mirrorHeaderSearch,
138
+ },
139
+ {
140
+ sel: 'scb-header',
141
+ from: 'search-click',
142
+ to: 'search',
143
+ mirror: mirrorHeaderSearch,
144
+ },
145
+
146
+ // scb-accordion-item
147
+ // Lyssnar på open-changed, uppdaterar egenskapen open och skickar openchange
148
+ {
149
+ sel: 'scb-accordion-item',
150
+ from: 'open-changed',
151
+ to: 'openchange',
152
+ prop: 'open',
153
+ },
154
+
155
+ // scb-app-bar
156
+ {
157
+ sel: 'scb-app-bar',
158
+ from: 'title-changed',
159
+ to: 'titlechange',
160
+ prop: 'title',
161
+ },
162
+ {
163
+ sel: 'scb-app-bar',
164
+ from: 'position-changed',
165
+ to: 'positionchange',
166
+ prop: 'position',
167
+ },
168
+ { sel: 'scb-app-bar', from: 'type-changed', to: 'typechange' },
169
+ {
170
+ sel: 'scb-app-bar',
171
+ from: 'search-supporting-text-changed',
172
+ to: 'searchsupportingtextchange',
173
+ },
174
+
175
+ // scb-breadcrumb
176
+ {
177
+ sel: 'scb-breadcrumb',
178
+ from: 'show-all-changed',
179
+ to: 'showallchange',
180
+ prop: 'showAll',
181
+ },
182
+ {
183
+ sel: 'scb-breadcrumb-item',
184
+ from: 'current-changed',
185
+ to: 'currentchange',
186
+ prop: 'isCurrent',
187
+ },
188
+
189
+ // scb-button (click mappas inte här, Blazor kan använda @onclick direkt)
190
+ {
191
+ sel: 'scb-button',
192
+ from: 'variant-changed',
193
+ to: 'variantchange',
194
+ prop: 'variant',
195
+ },
196
+ {
197
+ sel: 'scb-button',
198
+ from: 'label-changed',
199
+ to: 'labelchange',
200
+ prop: 'label',
201
+ },
202
+ {
203
+ sel: 'scb-button',
204
+ from: 'disabled-changed',
205
+ to: 'disabledchange',
206
+ prop: 'disabled',
207
+ },
208
+
209
+ // scb-calendar-card
210
+ {
211
+ sel: 'scb-calendar-card',
212
+ from: 'title-changed',
213
+ to: 'titlechange',
214
+ prop: 'title',
215
+ },
216
+ {
217
+ sel: 'scb-calendar-card',
218
+ from: 'subtitle-changed',
219
+ to: 'subtitlechange',
220
+ prop: 'subtitle',
221
+ },
222
+ {
223
+ sel: 'scb-calendar-card',
224
+ from: 'show-media-changed',
225
+ to: 'showmediachange',
226
+ prop: 'showMedia',
227
+ },
228
+ {
229
+ sel: 'scb-calendar-card',
230
+ from: 'media-width-changed',
231
+ to: 'mediawidthchange',
232
+ prop: 'mediaWidth',
233
+ },
234
+ {
235
+ sel: 'scb-calendar-card',
236
+ from: 'media-height-changed',
237
+ to: 'mediaheightchange',
238
+ prop: 'mediaHeight',
239
+ },
240
+
241
+ // scb-card
242
+ {
243
+ sel: 'scb-card',
244
+ from: 'variant-changed',
245
+ to: 'variantchange',
246
+ prop: 'variant',
247
+ },
248
+ {
249
+ sel: 'scb-card',
250
+ from: 'direction-changed',
251
+ to: 'directionchange',
252
+ prop: 'direction',
253
+ },
254
+ {
255
+ sel: 'scb-card',
256
+ from: 'title-changed',
257
+ to: 'titlechange',
258
+ prop: 'title',
259
+ },
260
+ {
261
+ sel: 'scb-card',
262
+ from: 'subtitle-changed',
263
+ to: 'subtitlechange',
264
+ prop: 'subtitle',
265
+ },
266
+ {
267
+ sel: 'scb-card',
268
+ from: 'show-media-changed',
269
+ to: 'showmediachange',
270
+ prop: 'showMedia',
271
+ },
272
+ {
273
+ sel: 'scb-card',
274
+ from: 'media-width-changed',
275
+ to: 'mediawidthchange',
276
+ prop: 'mediaWidth',
277
+ },
278
+ {
279
+ sel: 'scb-card',
280
+ from: 'media-height-changed',
281
+ to: 'mediaheightchange',
282
+ prop: 'mediaHeight',
283
+ },
284
+
285
+ // scb-chip
286
+ { sel: 'scb-chip', from: 'remove', to: 'remove' },
287
+ {
288
+ sel: 'scb-chip',
289
+ from: 'selected-changed',
290
+ to: 'selectedchange',
291
+ prop: 'selected',
292
+ },
293
+ {
294
+ sel: 'scb-chip',
295
+ from: 'variant-changed',
296
+ to: 'variantchange',
297
+ prop: 'variant',
298
+ },
299
+ {
300
+ sel: 'scb-chip',
301
+ from: 'label-changed',
302
+ to: 'labelchange',
303
+ prop: 'label',
304
+ },
305
+ {
306
+ sel: 'scb-chip',
307
+ from: 'disabled-changed',
308
+ to: 'disabledchange',
309
+ prop: 'disabled',
310
+ },
311
+ {
312
+ sel: 'scb-chip',
313
+ from: 'elevated-changed',
314
+ to: 'elevatedchange',
315
+ prop: 'elevated',
316
+ },
317
+ {
318
+ sel: 'scb-chip',
319
+ from: 'icon-changed',
320
+ to: 'iconchange',
321
+ prop: 'icon',
322
+ },
323
+
324
+ // scb-checkbox
325
+ {
326
+ sel: 'scb-checkbox',
327
+ from: 'checked-changed',
328
+ to: 'checkedchange',
329
+ prop: 'checked',
330
+ },
331
+ {
332
+ sel: 'scb-checkbox',
333
+ from: 'disabled-changed',
334
+ to: 'disabledchange',
335
+ prop: 'disabled',
336
+ },
337
+ {
338
+ sel: 'scb-checkbox',
339
+ from: 'indeterminate-changed',
340
+ to: 'indeterminatechange',
341
+ prop: 'indeterminate',
342
+ },
343
+
344
+ // scb-checkbox-group
345
+ {
346
+ sel: 'scb-checkbox-group',
347
+ from: 'orientation-changed',
348
+ to: 'orientationchange',
349
+ prop: 'orientation',
350
+ },
351
+ {
352
+ sel: 'scb-checkbox-group',
353
+ from: 'disabled-changed',
354
+ to: 'disabledchange',
355
+ prop: 'disabled',
356
+ },
357
+ {
358
+ sel: 'scb-checkbox-group',
359
+ from: 'spacing-changed',
360
+ to: 'spacingchange',
361
+ prop: 'spacing',
362
+ },
363
+
364
+ // scb-dialog (actions mappas rakt vidare)
365
+ { sel: 'scb-dialog', from: 'ok', to: 'ok' },
366
+ { sel: 'scb-dialog', from: 'cancel', to: 'cancel' },
367
+ { sel: 'scb-dialog', from: 'confirm', to: 'confirm' },
368
+ { sel: 'scb-dialog', from: 'deny', to: 'deny' },
369
+ { sel: 'scb-dialog', from: 'reset', to: 'reset' },
370
+ { sel: 'scb-dialog', from: 'submit', to: 'submit' },
371
+ { sel: 'scb-dialog', from: 'esc', to: 'esc' },
372
+ { sel: 'scb-dialog', from: 'scrim', to: 'scrim' },
373
+
374
+ // scb-drawer
375
+ {
376
+ sel: 'scb-drawer',
377
+ from: 'scb-drawer-opened',
378
+ to: 'openchange',
379
+ prop: 'open',
380
+ },
381
+ {
382
+ sel: 'scb-drawer',
383
+ from: 'scb-drawer-closed',
384
+ to: 'openchange',
385
+ prop: 'open',
386
+ },
387
+
388
+ // scb-drawer-item
389
+ {
390
+ sel: 'scb-drawer-item',
391
+ from: 'scb-drawer-expand',
392
+ to: 'expandedchange',
393
+ },
394
+ { sel: 'scb-drawer-item', from: 'scb-drawer-select', to: 'select' },
395
+
396
+ // scb-fact-card
397
+ {
398
+ sel: 'scb-fact-card',
399
+ from: 'open',
400
+ to: 'openchange',
401
+ prop: 'open',
402
+ },
403
+ {
404
+ sel: 'scb-fact-card',
405
+ from: 'close',
406
+ to: 'openchange',
407
+ prop: 'open',
408
+ },
409
+
410
+ // scb-fact-card-content
411
+ {
412
+ sel: 'scb-fact-card-content',
413
+ from: 'label-changed',
414
+ to: 'labelchange',
415
+ prop: 'label',
416
+ },
417
+ {
418
+ sel: 'scb-fact-card-content',
419
+ from: 'sub-label-changed',
420
+ to: 'sublabelchange',
421
+ prop: 'subLabel',
422
+ },
423
+ {
424
+ sel: 'scb-fact-card-content',
425
+ from: 'supporting-text-changed',
426
+ to: 'supportingtextchange',
427
+ prop: 'supportingText',
428
+ },
429
+
430
+ // scb-horizontal-scroller
431
+ {
432
+ sel: 'scb-horizontal-scroller',
433
+ from: 'scb-scroll',
434
+ to: 'scroll',
435
+ prop: 'scrollLeft',
436
+ },
437
+ {
438
+ sel: 'scb-horizontal-scroller',
439
+ from: 'scb-scroll-start',
440
+ to: 'scrollstart',
441
+ prop: 'scrollLeft',
442
+ },
443
+ {
444
+ sel: 'scb-horizontal-scroller',
445
+ from: 'scb-scroll-end',
446
+ to: 'scrollend',
447
+ prop: 'scrollLeft',
448
+ },
449
+ {
450
+ sel: 'scb-horizontal-scroller',
451
+ from: 'scb-scroll-right',
452
+ to: 'scrollright',
453
+ prop: 'scrollLeft',
454
+ },
455
+ {
456
+ sel: 'scb-horizontal-scroller',
457
+ from: 'scb-scroll-left',
458
+ to: 'scrollleft',
459
+ prop: 'scrollLeft',
460
+ },
461
+
462
+ // scb-icon-button
463
+ {
464
+ sel: 'scb-icon-button',
465
+ from: 'change',
466
+ to: 'selectedchange',
467
+ prop: 'selected',
468
+ },
469
+ {
470
+ sel: 'scb-icon-button',
471
+ from: 'toggle-changed',
472
+ to: 'togglechange',
473
+ prop: 'toggle',
474
+ },
475
+ {
476
+ sel: 'scb-icon-button',
477
+ from: 'icon-changed',
478
+ to: 'iconchange',
479
+ prop: 'icon',
480
+ },
481
+ {
482
+ sel: 'scb-icon-button',
483
+ from: 'disabled-changed',
484
+ to: 'disabledchange',
485
+ prop: 'disabled',
486
+ },
487
+ {
488
+ sel: 'scb-icon-button',
489
+ from: 'tooltip-changed',
490
+ to: 'tooltipchange',
491
+ prop: 'tooltip',
492
+ },
493
+
494
+ // scb-menu
495
+ { sel: 'scb-menu', from: 'open', to: 'openchange', prop: 'open' },
496
+ { sel: 'scb-menu', from: 'close', to: 'openchange', prop: 'open' },
497
+ { sel: 'scb-menu', from: 'toggle', to: 'openchange', prop: 'open' },
498
+
499
+ // scb-search (fristående search-komponent, inte headern)
500
+ {
501
+ sel: 'scb-search',
502
+ from: 'scb-search-input',
503
+ to: 'valuechange',
504
+ prop: 'value',
505
+ },
506
+ {
507
+ sel: 'scb-search',
508
+ from: 'scb-search-submit',
509
+ to: 'search',
510
+ prop: 'value',
511
+ },
512
+ {
513
+ sel: 'scb-search',
514
+ from: 'scb-search-clear',
515
+ to: 'valuechange',
516
+ prop: 'value',
517
+ },
518
+
519
+ // scb-radio-group
520
+ { sel: 'scb-radio-group', from: 'scb-change', to: 'change' },
521
+
522
+ // scb-segmented-button
523
+ {
524
+ sel: 'scb-segmented-button',
525
+ from: 'change',
526
+ to: 'valuechange',
527
+ prop: 'value',
528
+ },
529
+ {
530
+ sel: 'scb-segmented-button',
531
+ from: 'change',
532
+ to: 'valueschange',
533
+ prop: 'values',
534
+ },
535
+
536
+ // scb-snackbar, hanterar öppna/stäng via egna events
537
+ {
538
+ sel: 'scb-snackbar',
539
+ from: 'snackbar-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-snackbar',
550
+ from: 'snackbar-close',
551
+ to: 'openchange',
552
+ mirror: (el) => {
553
+ try {
554
+ el.open = false;
555
+ } catch (_) {}
556
+ el.removeAttribute('open');
557
+ },
558
+ },
559
+ { sel: 'scb-snackbar', from: 'snackbar-action', to: 'action' },
560
+
561
+ // scb-notification-card, liknande hantering som snackbar
562
+ {
563
+ sel: 'scb-notification-card',
564
+ from: 'notification-open',
565
+ to: 'openchange',
566
+ mirror: (el) => {
567
+ try {
568
+ el.open = true;
569
+ } catch (_) {}
570
+ el.setAttribute('open', '');
571
+ },
572
+ },
573
+ {
574
+ sel: 'scb-notification-card',
575
+ from: 'notification-close',
576
+ to: 'openchange',
577
+ mirror: (el) => {
578
+ try {
579
+ el.open = false;
580
+ } catch (_) {}
581
+ el.removeAttribute('open');
582
+ },
583
+ },
584
+ {
585
+ sel: 'scb-notification-card',
586
+ from: 'close',
587
+ to: 'openchange',
588
+ mirror: (el) => {
589
+ try {
590
+ el.open = false;
591
+ } catch (_) {}
592
+ el.removeAttribute('open');
593
+ },
594
+ },
595
+
596
+ // scb-switch
597
+ {
598
+ sel: 'scb-switch',
599
+ from: 'change',
600
+ to: 'selectedchange',
601
+ prop: 'selected',
602
+ },
603
+
604
+ // scb-tabs
605
+ {
606
+ sel: 'scb-tabs',
607
+ from: 'change',
608
+ to: 'tabschange',
609
+ prop: 'activeTabIndex',
610
+ },
611
+
612
+ // scb-textfield
613
+ {
614
+ sel: 'scb-textfield',
615
+ from: 'onValueChanged',
616
+ to: 'valuechange',
617
+ prop: 'value',
618
+ },
619
+ {
620
+ sel: 'scb-textfield',
621
+ from: 'input',
622
+ to: 'input',
623
+ prop: 'value',
624
+ },
625
+ {
626
+ sel: 'scb-textfield',
627
+ from: 'change',
628
+ to: 'change',
629
+ prop: 'value',
630
+ },
631
+ { sel: 'scb-textfield', from: 'select', to: 'select' },
632
+
633
+ // scb-toc-item
634
+ {
635
+ sel: 'scb-toc-item',
636
+ from: 'expanded-changed',
637
+ to: 'expandedchange',
638
+ prop: 'expanded',
639
+ },
640
+
641
+ // scb-pagination
642
+ { sel: 'scb-pagination', from: 'page-change', to: 'pagechange' },
643
+
644
+ // scb-stepper (stegbyte i steppern)
645
+ {
646
+ sel: 'scb-stepper',
647
+ from: 'scb-step-change',
648
+ to: 'stepchange',
649
+ },
650
+ ];
651
+
652
+ /* Indexering av regler per source event för snabbare lookup vid dispatch. */
653
+ const bySource = new Map();
654
+ for (const r of RULES) {
655
+ if (!bySource.has(r.from)) bySource.set(r.from, []);
656
+ bySource.get(r.from).push(r);
657
+ }
658
+
659
+ /* Registrerar en global listener per source event.
660
+ Varje listener hittar relevant komponent och triggar rätt normaliserat event. */
661
+ bySource.forEach((rules, fromEvent) => {
662
+ document.addEventListener(
663
+ fromEvent,
664
+ (ev) => {
665
+ for (const rule of rules) {
666
+ const el = closestInPath(rule.sel, ev);
667
+ if (!el) continue;
668
+
669
+ // Uppdatera elementets state om en mirror function finns
670
+ if (rule.mirror) {
671
+ try {
672
+ rule.mirror(el, ev);
673
+ } catch (_) {}
674
+ } else {
675
+ const detail = ev.detail;
676
+ if (
677
+ rule.prop &&
678
+ detail &&
679
+ Object.prototype.hasOwnProperty.call(detail, rule.prop)
680
+ ) {
681
+ try {
682
+ el[rule.prop] = detail[rule.prop];
683
+ } catch (_) {}
684
+ }
685
+ }
686
+
687
+ // Skicka endast vidare om namn faktiskt ändras
688
+ if (rule.from !== rule.to) {
689
+ emit(el, rule.to, ev);
690
+ }
691
+ }
692
+ },
693
+ { capture: true },
694
+ );
695
+ });
696
+
697
+ /* MutationObserver som lyssnar på attributförändringar
698
+ och skickar motsvarande *change-event när något viktigt ändras. */
699
+ const ATTR_RULES = [
700
+ { sel: 'scb-drawer', attr: 'open', to: 'openchange' },
701
+ { sel: 'scb-fact-card', attr: 'open', to: 'openchange' },
702
+ // scb-accordion-item: emit openchange när attributet open ändras
703
+ { sel: 'scb-accordion-item', attr: 'open', to: 'openchange' },
704
+ { sel: 'scb-icon-button', attr: 'selected', to: 'selectedchange' },
705
+ { sel: 'scb-checkbox', attr: 'checked', to: 'checkedchange' },
706
+ { sel: 'scb-menu', attr: 'open', to: 'openchange' },
707
+ { sel: 'scb-sub-menu', attr: 'open', to: 'openchange' },
708
+ { sel: 'scb-chip', attr: 'selected', to: 'selectedchange' },
709
+ { sel: 'scb-notification-card', attr: 'open', to: 'openchange' },
710
+
711
+ // scb-breadcrumb attribut som ska trigga state-refresh
712
+ { sel: 'scb-breadcrumb', attr: 'show-all', to: 'showallchange' },
713
+ { sel: 'scb-breadcrumb-item', attr: 'is-current', to: 'currentchange' },
714
+ { sel: 'scb-breadcrumb-item', attr: 'current', to: 'currentchange' },
715
+
716
+ // scb-calendar-card attribut som ska trigga state-refresh
717
+ { sel: 'scb-calendar-card', attr: 'title', to: 'titlechange' },
718
+ { sel: 'scb-calendar-card', attr: 'subtitle', to: 'subtitlechange' },
719
+ {
720
+ sel: 'scb-calendar-card',
721
+ attr: 'supporting-text',
722
+ to: 'supportingtextchange',
723
+ },
724
+ { sel: 'scb-calendar-card', attr: 'variant', to: 'variantchange' },
725
+ { sel: 'scb-calendar-card', attr: 'show-media', to: 'showmediachange' },
726
+
727
+ // scb-keyfigure-card attribut som ska trigga state-refresh
728
+ { sel: 'scb-keyfigure-card', attr: 'keyfigure', to: 'keyfigurechange' },
729
+ { sel: 'scb-keyfigure-card', attr: 'subtitle', to: 'subtitlechange' },
730
+ {
731
+ sel: 'scb-keyfigure-card',
732
+ attr: 'supporting-text',
733
+ to: 'supportingtextchange',
734
+ },
735
+ {
736
+ sel: 'scb-keyfigure-card',
737
+ attr: 'card-href',
738
+ to: 'cardhrefchange',
739
+ },
740
+ { sel: 'scb-keyfigure-card', attr: 'unit', to: 'unitchange' },
741
+
742
+ // scb-radio-button
743
+ { sel: 'scb-radio-button', attr: 'checked', to: 'checkedchange' },
744
+ { sel: 'scb-radio-button', attr: 'disabled', to: 'disabledchange' },
745
+ {
746
+ sel: 'scb-radio-button',
747
+ attr: 'aria-disabled',
748
+ to: 'ariadisabledchange',
749
+ },
750
+
751
+ // scb-radio-group
752
+ { sel: 'scb-radio-group', attr: 'disabled', to: 'disabledchange' },
753
+ {
754
+ sel: 'scb-radio-group',
755
+ attr: 'orientation',
756
+ to: 'orientationchange',
757
+ },
758
+ { sel: 'scb-radio-group', attr: 'spacing', to: 'spacingchange' },
759
+ { sel: 'scb-radio-group', attr: 'aria-label', to: 'arialabelchange' },
760
+ { sel: 'scb-radio-group', attr: 'name', to: 'namechange' },
761
+ { sel: 'scb-radio-group', attr: 'role', to: 'rolechange' },
762
+ { sel: 'scb-radio-group', attr: 'value', to: 'valuechange' },
763
+ { sel: 'scb-radio-group', attr: 'dir', to: 'dirchange' },
764
+ { sel: 'scb-radio-group', attr: 'style', to: 'stylechange' },
765
+ { sel: 'scb-radio-group', attr: 'class', to: 'classchange' },
766
+ { sel: 'scb-radio-group', attr: 'id', to: 'idchange' },
767
+ { sel: 'scb-radio-group', attr: 'hidden', to: 'hiddenchange' },
768
+ { sel: 'scb-radio-group', attr: 'tabindex', to: 'tabindexchange' },
769
+ { sel: 'scb-radio-group', attr: 'title', to: 'titlechange' },
770
+ { sel: 'scb-radio-group', attr: 'lang', to: 'langchange' },
771
+ { sel: 'scb-radio-group', attr: 'data-*', to: 'datachange' },
772
+ { sel: 'scb-radio-group', attr: 'draggable', to: 'draggablechange' },
773
+ {
774
+ sel: 'scb-radio-group',
775
+ attr: 'spellcheck',
776
+ to: 'spellcheckchange',
777
+ },
778
+ {
779
+ sel: 'scb-radio-group',
780
+ attr: 'contenteditable',
781
+ to: 'contenteditablechange',
782
+ },
783
+
784
+ { sel: 'scb-snackbar', attr: 'open', to: 'openchange' },
785
+
786
+ // scb-status-pill
787
+ { sel: 'scb-status-pill', attr: 'status', to: 'statuschange' },
788
+ { sel: 'scb-status-pill', attr: 'label', to: 'labelchange' },
789
+ { sel: 'scb-status-pill', attr: 'show-icon', to: 'showiconchange' },
790
+
791
+ { sel: 'scb-switch', attr: 'selected', to: 'selectedchange' },
792
+ { sel: 'scb-switch', attr: 'disabled', to: 'disabledchange' },
793
+ { sel: 'scb-switch', attr: 'icons', to: 'iconschange' },
794
+ { sel: 'scb-switch', attr: 'full-width', to: 'fullwidthchange' },
795
+ { sel: 'scb-switch', attr: 'label', to: 'labelchange' },
796
+ { sel: 'scb-switch', attr: 'aria-label', to: 'arialabelchange' },
797
+ { sel: 'scb-tabs', attr: 'aria-label', to: 'arialabelchange' },
798
+ { sel: 'scb-primary-tab', attr: 'icon', to: 'iconchange' },
799
+ { sel: 'scb-primary-tab', attr: 'icon-only', to: 'icononlychange' },
800
+ { sel: 'scb-primary-tab', attr: 'inline-icon', to: 'inlineiconchange' },
801
+ { sel: 'scb-primary-tab', attr: 'aria-label', to: 'arialabelchange' },
802
+ { sel: 'scb-primary-tab', attr: 'active', to: 'activechange' },
803
+ { sel: 'scb-secondary-tab', attr: 'icon', to: 'iconchange' },
804
+ { sel: 'scb-secondary-tab', attr: 'icon-only', to: 'icononlychange' },
805
+ { sel: 'scb-secondary-tab', attr: 'aria-label', to: 'arialabelchange' },
806
+ { sel: 'scb-secondary-tab', attr: 'active', to: 'activechange' },
807
+ { sel: 'scb-textfield', attr: 'value', to: 'valuechange' },
808
+ { sel: 'scb-textfield', attr: 'label', to: 'labelchange' },
809
+ {
810
+ sel: 'scb-textfield',
811
+ attr: 'supporting-text',
812
+ to: 'supportingtextchange',
813
+ },
814
+ { sel: 'scb-textfield', attr: 'error-text', to: 'errortextchange' },
815
+ { sel: 'scb-textfield', attr: 'leading-icon', to: 'leadingiconchange' },
816
+ { sel: 'scb-textfield', attr: 'type', to: 'typechange' },
817
+ { sel: 'scb-textfield', attr: 'name', to: 'namechange' },
818
+ { sel: 'scb-textfield', attr: 'pattern', to: 'patternchange' },
819
+ { sel: 'scb-textfield', attr: 'disabled', to: 'disabledchange' },
820
+ { sel: 'scb-textfield', attr: 'required', to: 'requiredchange' },
821
+ { sel: 'scb-textfield', attr: 'error', to: 'errorchange' },
822
+ { sel: 'scb-textfield', attr: 'aria-label', to: 'arialabelchange' },
823
+
824
+ { sel: 'scb-toc', attr: 'detached', to: 'detachedchange' },
825
+ { sel: 'scb-toc-item', attr: 'expanded', to: 'expandedchange' },
826
+ { sel: 'scb-toc-item', attr: 'label', to: 'labelchange' },
827
+ {
828
+ sel: 'scb-toc-item',
829
+ attr: 'supporting-text',
830
+ to: 'supportingtextchange',
831
+ },
832
+ { sel: 'scb-toc-item', attr: 'divider', to: 'dividerchange' },
833
+ { sel: 'scb-toc-item', attr: 'item-href', to: 'itemhrefchange' },
834
+
835
+ { sel: 'scb-tooltip', attr: 'open', to: 'openchange' },
836
+ { sel: 'scb-tooltip', attr: 'variant', to: 'variantchange' },
837
+ { sel: 'scb-tooltip', attr: 'position', to: 'positionchange' },
838
+ { sel: 'scb-tooltip', attr: 'trigger', to: 'triggerchange' },
839
+ { sel: 'scb-tooltip', attr: 'arrow', to: 'arrowchange' },
840
+ { sel: 'scb-tooltip', attr: 'delay', to: 'delaychange' },
841
+ { sel: 'scb-tooltip', attr: 'offset', to: 'offsetchange' },
842
+ { sel: 'scb-tooltip', attr: 'label', to: 'labelchange' },
843
+ {
844
+ sel: 'scb-tooltip',
845
+ attr: 'supporting-text',
846
+ to: 'supportingtextchange',
847
+ },
848
+ { sel: 'scb-dialog', attr: 'open', to: 'openchange' },
849
+ { sel: 'scb-dialog', attr: 'variant', to: 'variantchange' },
850
+ { sel: 'scb-dialog', attr: 'label', to: 'labelchange' },
851
+ { sel: 'scb-dialog', attr: 'icon', to: 'iconchange' },
852
+ {
853
+ sel: 'scb-dialog',
854
+ attr: 'supporting-text',
855
+ to: 'supportingtextchange',
856
+ },
857
+ { sel: 'scb-dialog', attr: 'ok-button', to: 'okbuttonchange' },
858
+ { sel: 'scb-dialog', attr: 'cancel-button', to: 'cancelbuttonchange' },
859
+ { sel: 'scb-dialog', attr: 'delete-button', to: 'deletebuttonchange' },
860
+ { sel: 'scb-dialog', attr: 'scrim-close', to: 'scrimclosechange' },
861
+
862
+ { sel: 'scb-drawer-item', attr: 'selected', to: 'selectedchange' },
863
+ { sel: 'scb-drawer-item', attr: 'label', to: 'labelchange' },
864
+ {
865
+ sel: 'scb-drawer-item',
866
+ attr: 'leading-icon',
867
+ to: 'leadingiconchange',
868
+ },
869
+ { sel: 'scb-drawer-item', attr: 'item-href', to: 'itemhrefchange' },
870
+
871
+ { sel: 'scb-list', attr: 'no-divider', to: 'nodividerchange' },
872
+ { sel: 'scb-list-item', attr: 'type', to: 'typechange' },
873
+ { sel: 'scb-list-item', attr: 'label', to: 'labelchange' },
874
+ {
875
+ sel: 'scb-list-item',
876
+ attr: 'supporting-text',
877
+ to: 'supportingtextchange',
878
+ },
879
+ {
880
+ sel: 'scb-list-item',
881
+ attr: 'trailing-supporting-text',
882
+ to: 'trailingsupportingtextchange',
883
+ },
884
+ { sel: 'scb-list-item', attr: 'headline', to: 'headlinechange' },
885
+ {
886
+ sel: 'scb-list-item',
887
+ attr: 'trailing-headline',
888
+ to: 'trailingheadlinechange',
889
+ },
890
+ { sel: 'scb-list-item', attr: 'href', to: 'hrefchange' },
891
+ { sel: 'scb-list-item', attr: 'current', to: 'currentchange' },
892
+ { sel: 'scb-list-item', attr: 'selected', to: 'selectedchange' },
893
+ { sel: 'scb-list-item', attr: 'checkbox', to: 'checkboxchange' },
894
+ { sel: 'scb-list-item', attr: 'avatar', to: 'avatarchange' },
895
+ { sel: 'scb-list-item', attr: 'overline', to: 'overlinechange' },
896
+ { sel: 'scb-list-item', attr: 'two-line', to: 'twolinechange' },
897
+ { sel: 'scb-list-item', attr: 'three-line', to: 'threelinechange' },
898
+ { sel: 'scb-list-item', attr: 'no-divider', to: 'nodividerchange' },
899
+ {
900
+ sel: 'scb-list-item',
901
+ attr: 'leading-variant',
902
+ to: 'leadingvariantchange',
903
+ },
904
+ {
905
+ sel: 'scb-list-item',
906
+ attr: 'trailing-variant',
907
+ to: 'trailingvariantchange',
908
+ },
909
+ { sel: 'scb-list-item', attr: 'density', to: 'densitychange' },
910
+ {
911
+ sel: 'scb-list-item',
912
+ attr: 'img-href-image',
913
+ to: 'imghrefimagechange',
914
+ },
915
+
916
+ { sel: 'scb-menu-item', attr: 'leading-icon', to: 'leadingiconchange' },
917
+ {
918
+ sel: 'scb-menu-item',
919
+ attr: 'trailing-icon',
920
+ to: 'trailingiconchange',
921
+ },
922
+ { sel: 'scb-menu-item', attr: 'item-href', to: 'itemhrefchange' },
923
+
924
+ {
925
+ sel: 'scb-notification-card',
926
+ attr: 'direction',
927
+ to: 'directionchange',
928
+ },
929
+ {
930
+ sel: 'scb-notification-card',
931
+ attr: 'show-icon',
932
+ to: 'showiconchange',
933
+ },
934
+ {
935
+ sel: 'scb-notification-card',
936
+ attr: 'show-close-button',
937
+ to: 'showclosebuttonchange',
938
+ },
939
+ {
940
+ sel: 'scb-notification-card',
941
+ attr: 'full-height',
942
+ to: 'fullheightchange',
943
+ },
944
+ {
945
+ sel: 'scb-notification-card',
946
+ attr: 'full-width',
947
+ to: 'fullwidthchange',
948
+ },
949
+
950
+ {
951
+ sel: 'scb-progress-indicator',
952
+ attr: 'type',
953
+ to: 'typechange',
954
+ },
955
+ {
956
+ sel: 'scb-progress-indicator',
957
+ attr: 'progress',
958
+ to: 'progresschange',
959
+ },
960
+ {
961
+ sel: 'scb-progress-indicator',
962
+ attr: 'is-static',
963
+ to: 'isstaticchange',
964
+ },
965
+
966
+ // scb-segmented-button: ändring av value-attribut ska trigga valuechange
967
+ { sel: 'scb-segmented-button', attr: 'value', to: 'valuechange' },
968
+
969
+ // scb-search: ändring av value-attribut ska trigga valuechange
970
+ { sel: 'scb-search', attr: 'value', to: 'valuechange' },
971
+
972
+ // scb-pagination: ändring av page-attribut ska trigga pagechange
973
+ { sel: 'scb-pagination', attr: 'page', to: 'pagechange' },
974
+
975
+ // scb-viz: attribut som ska trigga state-refresh
976
+ { sel: 'scb-viz', attr: 'variant', to: 'variantchange' },
977
+ { sel: 'scb-viz', attr: 'title', to: 'titlechange' },
978
+ { sel: 'scb-viz', attr: 'subtitle', to: 'subtitlechange' },
979
+ { sel: 'scb-viz', attr: 'description', to: 'descriptionchange' },
980
+ { sel: 'scb-viz', attr: 'comment', to: 'commentchange' },
981
+ { sel: 'scb-viz', attr: 'source', to: 'sourcechange' },
982
+ { sel: 'scb-viz', attr: 'footnote', to: 'footnotechange' },
983
+ { sel: 'scb-viz', attr: 'lang', to: 'langchange' },
984
+ { sel: 'scb-viz', attr: 'image-href', to: 'imagehrefchange' },
985
+ ];
986
+
987
+ /* Indexering av attributregler per attributnamn så att observern kan köra snabbare lookup. */
988
+ const ATTR_INDEX = new Map();
989
+ for (const r of ATTR_RULES) {
990
+ if (!ATTR_INDEX.has(r.attr)) ATTR_INDEX.set(r.attr, []);
991
+ ATTR_INDEX.get(r.attr).push(r);
992
+ }
993
+
994
+ /* Själva MutationObservern som bevakar attributändringar på alla relevanta element. */
995
+ const observer = new MutationObserver((mutations) => {
996
+ for (const m of mutations) {
997
+ if (m.type !== 'attributes') continue;
998
+ const el = m.target;
999
+ const attr = m.attributeName;
1000
+ if (!(el instanceof Element) || !attr) continue;
1001
+ const candidates = ATTR_INDEX.get(attr);
1002
+ if (!candidates) continue;
1003
+ for (const r of candidates) {
1004
+ if (el.matches(r.sel)) emit(el, r.to);
1005
+ }
1006
+ }
1007
+ });
1008
+
1009
+ observer.observe(document.documentElement, {
1010
+ subtree: true,
1011
+ attributes: true,
1012
+ attributeFilter: Array.from(ATTR_INDEX.keys()),
1013
+ });
1014
+ })();
1015
+
1016
+ /* Globalt SCBBlazor-objekt med getState som läser av aktuellt state för alla relevanta komponenter på sidan. */
1017
+ window.SCBBlazor = window.SCBBlazor || {};
1018
+ window.SCBBlazor.getState = function () {
1019
+ const header = document.querySelector('scb-header');
1020
+ const drawer = document.querySelector('scb-drawer');
1021
+ const breadcrumb = document.querySelector('scb-breadcrumb');
1022
+ const breadcrumbItems = document.querySelectorAll('scb-breadcrumb-item');
1023
+ const accordions = document.querySelectorAll('scb-accordion');
1024
+ const checkboxes = document.querySelectorAll('scb-checkbox');
1025
+ const switches = document.querySelectorAll('scb-switch');
1026
+ const appBar = document.querySelector('scb-app-bar');
1027
+ const segmented = document.querySelector('scb-segmented-button');
1028
+ const tabs = document.querySelectorAll('scb-tabs');
1029
+ const calendars = document.querySelectorAll('scb-calendar-card');
1030
+ const cards = document.querySelectorAll('scb-card');
1031
+ const keyfigures = document.querySelectorAll('scb-keyfigure-card');
1032
+ const menu = document.querySelector('scb-menu');
1033
+ const dialog = document.querySelector('scb-dialog');
1034
+ const notifications = document.querySelectorAll('scb-notification-card');
1035
+ const radioGroups = document.querySelectorAll('scb-radio-group');
1036
+ const paginations = document.querySelectorAll('scb-pagination');
1037
+ const snackbars = document.querySelectorAll('scb-snackbar');
1038
+ const statusPills = document.querySelectorAll('scb-status-pill');
1039
+ const stepper = document.querySelector('scb-stepper');
1040
+ const tooltips = document.querySelectorAll('scb-tooltip');
1041
+ const textfields = document.querySelectorAll('scb-textfield');
1042
+ const searches = document.querySelectorAll('scb-search');
1043
+ const tocs = document.querySelectorAll('scb-toc');
1044
+ const vizs = document.querySelectorAll('scb-viz');
1045
+ const lists = document.querySelectorAll('scb-list');
1046
+ const links = document.querySelectorAll('scb-link');
1047
+ const subMenus = document.querySelectorAll('scb-sub-menu');
1048
+
1049
+ // Headerstate hämtas från attributen på första scb-header
1050
+ const headerState = {
1051
+ activeTab: header
1052
+ ? parseInt(header.getAttribute('active-tab') || '0', 10) || 0
1053
+ : 0,
1054
+ drawerOpen: header ? header.hasAttribute('drawer-open') : false,
1055
+ searchText: header ? header.getAttribute('search-text') || '' : '',
1056
+ };
1057
+
1058
+ // Drawerstate hämtas från attributen på första scb-drawer
1059
+ const drawerState = {
1060
+ open: drawer ? drawer.hasAttribute('open') : false,
1061
+ label: drawer ? drawer.getAttribute('label') || '' : '',
1062
+ subLabel: drawer ? drawer.getAttribute('sub-label') || '' : '',
1063
+ };
1064
+
1065
+ // Breadcrumb (första på sidan)
1066
+ const breadcrumbState = {
1067
+ showAll: breadcrumb
1068
+ ? !!(
1069
+ breadcrumb.showAll ?? breadcrumb.hasAttribute('show-all')
1070
+ )
1071
+ : false,
1072
+ };
1073
+
1074
+ const breadcrumbItemsState = Array.from(breadcrumbItems).map((item) => {
1075
+ const anyItem = item;
1076
+ const label = (
1077
+ anyItem.label ?? item.getAttribute('label') ?? ''
1078
+ ).toString();
1079
+
1080
+ const href = (
1081
+ anyItem.href ??
1082
+ anyItem.itemHref ??
1083
+ item.getAttribute('item-href') ??
1084
+ item.getAttribute('href') ??
1085
+ ''
1086
+ ).toString();
1087
+
1088
+ const isCurrent =
1089
+ typeof anyItem.isCurrent === 'boolean'
1090
+ ? anyItem.isCurrent
1091
+ : item.hasAttribute('is-current') || item.hasAttribute('current');
1092
+
1093
+ return {
1094
+ label,
1095
+ href,
1096
+ isCurrent: !!isCurrent,
1097
+ };
1098
+ });
1099
+
1100
+ // Generell modell för alla scb-accordion med sina scb-accordion-item
1101
+ const accordionsState = Array.from(accordions).map((acc) => {
1102
+ const items = acc.querySelectorAll('scb-accordion-item');
1103
+ return {
1104
+ detached: acc.hasAttribute('detached'),
1105
+ items: Array.from(items).map((item) => ({
1106
+ open: item.hasAttribute('open'),
1107
+ title: item.getAttribute('title') || '',
1108
+ overline: item.getAttribute('overline') || '',
1109
+ supportingText: item.getAttribute('supporting-text') || '',
1110
+ })),
1111
+ };
1112
+ });
1113
+
1114
+ const checkboxesState = Array.from(checkboxes).map((cb) => {
1115
+ const anyCb = cb;
1116
+ const checked =
1117
+ typeof anyCb.checked === 'boolean'
1118
+ ? anyCb.checked
1119
+ : cb.hasAttribute('checked');
1120
+
1121
+ return {
1122
+ checked: !!checked,
1123
+ name: cb.getAttribute('name') || '',
1124
+ value: cb.getAttribute('value') || '',
1125
+ label: cb.getAttribute('label') || '',
1126
+ };
1127
+ });
1128
+
1129
+ // Switchar på sidan hämtas som objekt med selected, name, value och label
1130
+ const switchesState = Array.from(switches).map((sw) => {
1131
+ const anySw = sw;
1132
+ const selected =
1133
+ typeof anySw.selected === 'boolean'
1134
+ ? anySw.selected
1135
+ : sw.hasAttribute('selected');
1136
+
1137
+ return {
1138
+ selected: !!selected,
1139
+ name: sw.getAttribute('name') || '',
1140
+ value: sw.getAttribute('value') || '',
1141
+ label: sw.getAttribute('label') || '',
1142
+ };
1143
+ });
1144
+
1145
+ // App bar, läser direkt från attributen
1146
+ const appBarState = {
1147
+ title: appBar ? appBar.getAttribute('title') || '' : '',
1148
+ type: appBar ? appBar.getAttribute('type') || '' : '',
1149
+ position: appBar ? appBar.getAttribute('position') || '' : '',
1150
+ searchSupportingText: appBar
1151
+ ? appBar.getAttribute('search-supporting-text') || ''
1152
+ : '',
1153
+ };
1154
+
1155
+ // Första scb-segmented-button på sidan
1156
+ let segValue = '';
1157
+ let segValues = [];
1158
+
1159
+ if (segmented) {
1160
+ const hostValue =
1161
+ segmented.value ?? segmented.getAttribute('value') ?? '';
1162
+ const hostValues = Array.isArray(segmented.values)
1163
+ ? segmented.values
1164
+ : [];
1165
+
1166
+ if (hostValue || (hostValues && hostValues.length)) {
1167
+ const firstHostValue =
1168
+ hostValues && hostValues.length ? hostValues[0] : '';
1169
+ segValue = String(hostValue || firstHostValue);
1170
+ if (hostValues && hostValues.length) {
1171
+ segValues = hostValues.slice();
1172
+ } else if (segValue) {
1173
+ segValues = [segValue];
1174
+ }
1175
+ }
1176
+ }
1177
+
1178
+ const segmentedState = {
1179
+ value: segValue,
1180
+ values: segValues,
1181
+ };
1182
+
1183
+ // Alla scb-tabs med aktiv index per tabbar
1184
+ const tabsState = Array.from(tabs).map((t) => {
1185
+ const anyT = t;
1186
+ let activeIndex = 0;
1187
+
1188
+ if (typeof anyT.activeTabIndex === 'number') {
1189
+ activeIndex = anyT.activeTabIndex;
1190
+ } else {
1191
+ const attrVal = t.getAttribute('active-tab-index');
1192
+ if (attrVal != null) {
1193
+ const parsed = parseInt(attrVal, 10);
1194
+ if (!Number.isNaN(parsed)) {
1195
+ activeIndex = parsed;
1196
+ }
1197
+ }
1198
+ }
1199
+
1200
+ return { activeIndex };
1201
+ });
1202
+
1203
+ // Alla scb-calendar-card på sidan
1204
+ const calendarsState = Array.from(calendars).map((c) => ({
1205
+ title: c.getAttribute('title') || '',
1206
+ subtitle: c.getAttribute('subtitle') || '',
1207
+ supportingText: c.getAttribute('supporting-text') || '',
1208
+ variant: c.getAttribute('variant') || '',
1209
+ showMedia: c.hasAttribute('show-media'),
1210
+ }));
1211
+
1212
+ // Enkelt kalenderläge för den första kalendern
1213
+ const calendarState =
1214
+ calendarsState.length > 0
1215
+ ? calendarsState[0]
1216
+ : {
1217
+ title: '',
1218
+ subtitle: '',
1219
+ supportingText: '',
1220
+ variant: '',
1221
+ showMedia: false,
1222
+ };
1223
+
1224
+ // Alla scb-card på sidan
1225
+ const cardsState = Array.from(cards).map((card) => ({
1226
+ type: card.getAttribute('type') || '',
1227
+ variant: card.getAttribute('variant') || '',
1228
+ direction: card.getAttribute('direction') || '',
1229
+ title: card.getAttribute('title') || '',
1230
+ subtitle: card.getAttribute('subtitle') || '',
1231
+ supportingText: card.getAttribute('supporting-text') || '',
1232
+ cardHref: card.getAttribute('card-href') || '',
1233
+ }));
1234
+
1235
+ // Alla scb-keyfigure-card på sidan
1236
+ const keyfiguresState = Array.from(keyfigures).map((card) => ({
1237
+ keyfigure: card.getAttribute('keyfigure') || '',
1238
+ subtitle: card.getAttribute('subtitle') || '',
1239
+ supportingText: card.getAttribute('supporting-text') || '',
1240
+ cardHref: card.getAttribute('card-href') || '',
1241
+ icon: card.getAttribute('icon') || '',
1242
+ size: card.getAttribute('size') || '',
1243
+ unit: card.getAttribute('unit') || '',
1244
+ }));
1245
+
1246
+ // Meny (första scb-menu)
1247
+ const menuState = {
1248
+ open: menu ? menu.hasAttribute('open') : false,
1249
+ };
1250
+
1251
+ // Alla notification cards på sidan
1252
+ const notificationsState = Array.from(notifications).map((card) => ({
1253
+ open: card.hasAttribute('open'),
1254
+ variant: card.getAttribute('variant') || '',
1255
+ title: card.getAttribute('title') || '',
1256
+ subtitle: card.getAttribute('subtitle') || '',
1257
+ supportingText: card.getAttribute('supporting-text') || '',
1258
+ linkText: card.getAttribute('link-text') || '',
1259
+ linkHref: card.getAttribute('link-href') || '',
1260
+ showIcon: card.hasAttribute('show-icon'),
1261
+ showCloseButton: card.hasAttribute('show-close-button'),
1262
+ fullHeight: card.hasAttribute('full-height'),
1263
+ fullWidth: card.hasAttribute('full-width'),
1264
+ direction: card.getAttribute('direction') || '',
1265
+ }));
1266
+
1267
+ // Alla snackbars, med stöd för både attribut och properties
1268
+ const snackbarsState = Array.from(snackbars).map((sb) => {
1269
+ const anySb = sb;
1270
+ const message =
1271
+ (anySb.message ?? sb.getAttribute('message') ?? '').toString();
1272
+ const actionText =
1273
+ (anySb.actionText ?? sb.getAttribute('action-text') ?? '').toString();
1274
+ const showClose =
1275
+ typeof anySb.showClose === 'boolean'
1276
+ ? anySb.showClose
1277
+ : sb.hasAttribute('show-close');
1278
+ const fixed =
1279
+ typeof anySb.fixed === 'boolean'
1280
+ ? anySb.fixed
1281
+ : sb.hasAttribute('fixed');
1282
+ const fadeout =
1283
+ typeof anySb.fadeout === 'boolean'
1284
+ ? anySb.fadeout
1285
+ : sb.hasAttribute('fadeout');
1286
+ const withLongerAction =
1287
+ typeof anySb.withLongerAction === 'boolean'
1288
+ ? anySb.withLongerAction
1289
+ : sb.hasAttribute('with-longer-action');
1290
+
1291
+ return {
1292
+ open: sb.hasAttribute('open'),
1293
+ message,
1294
+ actionText,
1295
+ showClose,
1296
+ fixed,
1297
+ fadeout,
1298
+ withLongerAction,
1299
+ };
1300
+ });
1301
+
1302
+ // Alla status pills på sidan
1303
+ const statusPillsState = Array.from(statusPills).map((sp) => {
1304
+ const anySp = sp;
1305
+ const status =
1306
+ (anySp.status ?? sp.getAttribute('status') ?? '').toString();
1307
+ const label =
1308
+ (anySp.label ?? sp.getAttribute('label') ?? '').toString();
1309
+ const showIcon =
1310
+ typeof anySp.showIcon === 'boolean'
1311
+ ? anySp.showIcon
1312
+ : sp.hasAttribute('show-icon');
1313
+
1314
+ return {
1315
+ status,
1316
+ label,
1317
+ showIcon,
1318
+ };
1319
+ });
1320
+
1321
+ // Alla tooltips på sidan
1322
+ const tooltipsState = Array.from(tooltips).map((tooltip) => {
1323
+ const anyTooltip = tooltip;
1324
+ const open =
1325
+ typeof anyTooltip.open === 'boolean'
1326
+ ? anyTooltip.open
1327
+ : tooltip.hasAttribute('open');
1328
+
1329
+ const variant = (
1330
+ anyTooltip.variant ??
1331
+ tooltip.getAttribute('variant') ??
1332
+ ''
1333
+ ).toString();
1334
+
1335
+ const position = (
1336
+ anyTooltip.position ??
1337
+ tooltip.getAttribute('position') ??
1338
+ ''
1339
+ ).toString();
1340
+
1341
+ const trigger = (
1342
+ anyTooltip.trigger ??
1343
+ tooltip.getAttribute('trigger') ??
1344
+ ''
1345
+ ).toString();
1346
+
1347
+ let delay = 0;
1348
+ if (typeof anyTooltip.delay === 'number') {
1349
+ delay = anyTooltip.delay;
1350
+ } else {
1351
+ const attrDelay = tooltip.getAttribute('delay');
1352
+ const parsed =
1353
+ attrDelay != null ? Number.parseFloat(attrDelay) : Number.NaN;
1354
+ delay = Number.isNaN(parsed) ? 0 : parsed;
1355
+ }
1356
+
1357
+ let offset = 0;
1358
+ if (typeof anyTooltip.offset === 'number') {
1359
+ offset = anyTooltip.offset;
1360
+ } else {
1361
+ const attrOffset = tooltip.getAttribute('offset');
1362
+ const parsed =
1363
+ attrOffset != null ? Number.parseFloat(attrOffset) : Number.NaN;
1364
+ offset = Number.isNaN(parsed) ? 0 : parsed;
1365
+ }
1366
+
1367
+ const label = (
1368
+ anyTooltip.label ??
1369
+ tooltip.getAttribute('label') ??
1370
+ ''
1371
+ ).toString();
1372
+
1373
+ const supportingText = (
1374
+ anyTooltip.supportingtext ??
1375
+ tooltip.getAttribute('supporting-text') ??
1376
+ ''
1377
+ ).toString();
1378
+
1379
+ return {
1380
+ open: !!open,
1381
+ variant,
1382
+ position,
1383
+ trigger,
1384
+ delay,
1385
+ offset,
1386
+ label,
1387
+ supportingText,
1388
+ };
1389
+ });
1390
+
1391
+ // Alla radio groups med namn och vald radios value
1392
+ const radioGroupsState = Array.from(radioGroups).map((group) => {
1393
+ const radios = group.querySelectorAll('scb-radio-button');
1394
+ let value = '';
1395
+ for (const rb of Array.from(radios)) {
1396
+ if (rb.hasAttribute('checked')) {
1397
+ value = rb.getAttribute('value') || '';
1398
+ break;
1399
+ }
1400
+ }
1401
+ return {
1402
+ name: group.getAttribute('name') || '',
1403
+ value,
1404
+ };
1405
+ });
1406
+
1407
+ // Alla pagination-komponenter med aktuell sida och antal sidor
1408
+ const paginationsState = Array.from(paginations).map((p) => {
1409
+ const el = p;
1410
+ const pageProp =
1411
+ typeof el.page === 'number'
1412
+ ? el.page
1413
+ : parseInt(p.getAttribute('page') || '0', 10) || 0;
1414
+
1415
+ const totalPagesProp =
1416
+ typeof el.totalPages === 'number'
1417
+ ? el.totalPages
1418
+ : parseInt(p.getAttribute('total-pages') || '0', 10) || 0;
1419
+
1420
+ return {
1421
+ page: pageProp,
1422
+ totalPages: totalPagesProp,
1423
+ };
1424
+ });
1425
+
1426
+ // Dialog (första scb-dialog på sidan)
1427
+ const dialogState = {
1428
+ open: dialog ? dialog.hasAttribute('open') : false,
1429
+ variant: dialog ? dialog.getAttribute('variant') || '' : '',
1430
+ label: dialog ? dialog.getAttribute('label') || '' : '',
1431
+ };
1432
+
1433
+ // Stepper (första scb-stepper på sidan) med aktivt steg och antal steg
1434
+ const stepperState = (function () {
1435
+ if (!stepper) {
1436
+ return { activeIndex: 0, totalSteps: 0 };
1437
+ }
1438
+
1439
+ const anyStepper = stepper;
1440
+ const steps = stepper.querySelectorAll('scb-step');
1441
+ const totalSteps = steps.length;
1442
+
1443
+ let activeIndex = 0;
1444
+
1445
+ if (typeof anyStepper.activeIndex === 'number') {
1446
+ activeIndex = anyStepper.activeIndex;
1447
+ } else {
1448
+ const attrVal = stepper.getAttribute('active-index');
1449
+ if (attrVal != null) {
1450
+ const parsed = parseInt(attrVal, 10);
1451
+ if (!Number.isNaN(parsed)) {
1452
+ activeIndex = parsed;
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ return { activeIndex, totalSteps };
1458
+ })();
1459
+
1460
+ // Alla textfält på sidan
1461
+ const textfieldsState = Array.from(textfields).map((tf) => {
1462
+ const anyTf = tf;
1463
+ const value = (
1464
+ anyTf.value ??
1465
+ tf.getAttribute('value') ??
1466
+ ''
1467
+ ).toString();
1468
+
1469
+ const label = (
1470
+ anyTf.label ??
1471
+ tf.getAttribute('label') ??
1472
+ ''
1473
+ ).toString();
1474
+
1475
+ const supportingText = (
1476
+ anyTf.supportingText ??
1477
+ tf.getAttribute('supporting-text') ??
1478
+ ''
1479
+ ).toString();
1480
+
1481
+ const error =
1482
+ typeof anyTf.error === 'boolean'
1483
+ ? anyTf.error
1484
+ : tf.hasAttribute('error');
1485
+
1486
+ const name = (
1487
+ anyTf.name ??
1488
+ tf.getAttribute('name') ??
1489
+ ''
1490
+ ).toString();
1491
+
1492
+ const id = (
1493
+ anyTf.id ??
1494
+ tf.getAttribute('id') ??
1495
+ ''
1496
+ ).toString();
1497
+
1498
+ return {
1499
+ value,
1500
+ label,
1501
+ supportingText,
1502
+ error,
1503
+ name,
1504
+ id,
1505
+ };
1506
+ });
1507
+
1508
+ // Alla search-komponenter på sidan
1509
+ const searchesState = Array.from(searches).map((s) => {
1510
+ const anyS = s;
1511
+ const value = (
1512
+ anyS.value ??
1513
+ s.getAttribute('value') ??
1514
+ ''
1515
+ ).toString();
1516
+
1517
+ const supportingText = (
1518
+ anyS.supportingText ??
1519
+ s.getAttribute('supporting-text') ??
1520
+ ''
1521
+ ).toString();
1522
+
1523
+ const fullScreen =
1524
+ typeof anyS.fullScreen === 'boolean'
1525
+ ? anyS.fullScreen
1526
+ : s.hasAttribute('full-screen');
1527
+
1528
+ const size = (
1529
+ anyS.size ??
1530
+ s.getAttribute('size') ??
1531
+ ''
1532
+ ).toString();
1533
+
1534
+ const name = (
1535
+ anyS.name ??
1536
+ s.getAttribute('name') ??
1537
+ ''
1538
+ ).toString();
1539
+
1540
+ const id = (
1541
+ anyS.id ??
1542
+ s.getAttribute('id') ??
1543
+ ''
1544
+ ).toString();
1545
+
1546
+ return {
1547
+ value,
1548
+ supportingText,
1549
+ fullScreen,
1550
+ size,
1551
+ name,
1552
+ id,
1553
+ };
1554
+ });
1555
+
1556
+ // Alla scb-toc på sidan med sina scb-toc-item
1557
+ const tocsState = Array.from(tocs).map((toc) => {
1558
+ const items = toc.querySelectorAll('scb-toc-item');
1559
+ return {
1560
+ detached: toc.hasAttribute('detached'),
1561
+ items: Array.from(items).map((item) => {
1562
+ const anyItem = item;
1563
+ const expanded =
1564
+ typeof anyItem.expanded === 'boolean'
1565
+ ? anyItem.expanded
1566
+ : false;
1567
+ const label = (
1568
+ anyItem.label ??
1569
+ item.getAttribute('label') ??
1570
+ ''
1571
+ ).toString();
1572
+ const supportingText = (
1573
+ anyItem.supportingText ??
1574
+ item.getAttribute('supporting-text') ??
1575
+ ''
1576
+ ).toString();
1577
+ const itemHref = (
1578
+ anyItem.itemHref ??
1579
+ item.getAttribute('item-href') ??
1580
+ ''
1581
+ ).toString();
1582
+ const divider =
1583
+ typeof anyItem.divider === 'boolean'
1584
+ ? anyItem.divider
1585
+ : item.hasAttribute('divider');
1586
+
1587
+ return {
1588
+ expanded: !!expanded,
1589
+ label,
1590
+ supportingText,
1591
+ itemHref,
1592
+ divider,
1593
+ };
1594
+ }),
1595
+ };
1596
+ });
1597
+
1598
+ // Alla scb-viz på sidan
1599
+ const vizState = Array.from(vizs).map((v) => {
1600
+ const anyV = v;
1601
+ const variant = (
1602
+ anyV.variant ??
1603
+ v.getAttribute('variant') ??
1604
+ ''
1605
+ ).toString();
1606
+ const title = (
1607
+ anyV.title ??
1608
+ v.getAttribute('title') ??
1609
+ ''
1610
+ ).toString();
1611
+ const subtitle = (
1612
+ anyV.subtitle ??
1613
+ v.getAttribute('subtitle') ??
1614
+ ''
1615
+ ).toString();
1616
+ const description = (
1617
+ anyV.description ??
1618
+ v.getAttribute('description') ??
1619
+ ''
1620
+ ).toString();
1621
+ const comment = (
1622
+ anyV.comment ??
1623
+ v.getAttribute('comment') ??
1624
+ ''
1625
+ ).toString();
1626
+ const source = (
1627
+ anyV.source ??
1628
+ v.getAttribute('source') ??
1629
+ ''
1630
+ ).toString();
1631
+ const footnote = (
1632
+ anyV.footnote ??
1633
+ v.getAttribute('footnote') ??
1634
+ ''
1635
+ ).toString();
1636
+ const lang = (
1637
+ anyV.lang ??
1638
+ v.getAttribute('lang') ??
1639
+ ''
1640
+ ).toString();
1641
+ const imageHref = (
1642
+ anyV.imageHref ??
1643
+ v.getAttribute('image-href') ??
1644
+ ''
1645
+ ).toString();
1646
+ const selectedChip = (
1647
+ anyV.selectedChip ??
1648
+ ''
1649
+ ).toString();
1650
+
1651
+ return {
1652
+ variant,
1653
+ title,
1654
+ subtitle,
1655
+ description,
1656
+ comment,
1657
+ source,
1658
+ footnote,
1659
+ lang,
1660
+ imageHref,
1661
+ selectedChip,
1662
+ };
1663
+ });
1664
+
1665
+ // Alla scb-list med sina scb-list-item
1666
+ const listsState = Array.from(lists).map((list) => {
1667
+ const items = list.querySelectorAll('scb-list-item');
1668
+ return {
1669
+ noDivider: list.hasAttribute('no-divider'),
1670
+ items: Array.from(items).map((item) => {
1671
+ const anyItem = item;
1672
+ const type = (
1673
+ anyItem.type ??
1674
+ item.getAttribute('type') ??
1675
+ ''
1676
+ ).toString();
1677
+ const label = (
1678
+ anyItem.label ??
1679
+ item.getAttribute('label') ??
1680
+ ''
1681
+ ).toString();
1682
+ const supportingText = (
1683
+ anyItem.supportingText ??
1684
+ item.getAttribute('supporting-text') ??
1685
+ ''
1686
+ ).toString();
1687
+ const overline = (
1688
+ anyItem.overline ??
1689
+ item.getAttribute('overline') ??
1690
+ ''
1691
+ ).toString();
1692
+ const leading =
1693
+ typeof anyItem.leading === 'boolean'
1694
+ ? anyItem.leading
1695
+ : item.hasAttribute('leading');
1696
+ const leadingVariant = (
1697
+ anyItem.leadingVariant ??
1698
+ item.getAttribute('leading-variant') ??
1699
+ ''
1700
+ ).toString();
1701
+ const leadingIcon = (
1702
+ anyItem.leadingIcon ??
1703
+ item.getAttribute('leading-icon') ??
1704
+ ''
1705
+ ).toString();
1706
+ const trailing =
1707
+ typeof anyItem.trailing === 'boolean'
1708
+ ? anyItem.trailing
1709
+ : item.hasAttribute('trailing');
1710
+ const trailingVariant = (
1711
+ anyItem.trailingVariant ??
1712
+ item.getAttribute('trailing-variant') ??
1713
+ ''
1714
+ ).toString();
1715
+ const trailingIcon = (
1716
+ anyItem.trailingIcon ??
1717
+ item.getAttribute('trailing-icon') ??
1718
+ ''
1719
+ ).toString();
1720
+ const disabled =
1721
+ typeof anyItem.disabled === 'boolean'
1722
+ ? anyItem.disabled
1723
+ : item.hasAttribute('disabled');
1724
+ const href = (
1725
+ anyItem.href ??
1726
+ item.getAttribute('href') ??
1727
+ ''
1728
+ ).toString();
1729
+ const itemHref = (
1730
+ anyItem.itemHref ??
1731
+ item.getAttribute('item-href') ??
1732
+ ''
1733
+ ).toString();
1734
+ const target = (
1735
+ anyItem.target ??
1736
+ item.getAttribute('target') ??
1737
+ ''
1738
+ ).toString();
1739
+ let density = 0;
1740
+ if (typeof anyItem.density === 'number') {
1741
+ density = anyItem.density;
1742
+ } else {
1743
+ const attrDensity = item.getAttribute('density');
1744
+ const parsed =
1745
+ attrDensity != null ? Number.parseInt(attrDensity, 10) : Number.NaN;
1746
+ density = Number.isNaN(parsed) ? 0 : parsed;
1747
+ }
1748
+ const noDivider =
1749
+ typeof anyItem.noDivider === 'boolean'
1750
+ ? anyItem.noDivider
1751
+ : item.hasAttribute('no-divider');
1752
+ const imgHrefImage = (
1753
+ anyItem.imgHrefImage ??
1754
+ item.getAttribute('img-href-image') ??
1755
+ ''
1756
+ ).toString();
1757
+ const avatarLabel = (
1758
+ anyItem.avatarLabel ??
1759
+ item.getAttribute('avatar-label') ??
1760
+ ''
1761
+ ).toString();
1762
+ const avatarAlt = (
1763
+ anyItem.avatarAlt ??
1764
+ item.getAttribute('avatar-alt') ??
1765
+ ''
1766
+ ).toString();
1767
+ const avatarVariant = (
1768
+ anyItem.avatarVariant ??
1769
+ item.getAttribute('avatar-variant') ??
1770
+ ''
1771
+ ).toString();
1772
+ const avatarSrc = (
1773
+ anyItem.avatarSrc ??
1774
+ item.getAttribute('avatar-src') ??
1775
+ ''
1776
+ ).toString();
1777
+
1778
+ return {
1779
+ type,
1780
+ label,
1781
+ supportingText,
1782
+ overline,
1783
+ leading: !!leading,
1784
+ leadingVariant,
1785
+ leadingIcon,
1786
+ trailing: !!trailing,
1787
+ trailingVariant,
1788
+ trailingIcon,
1789
+ disabled: !!disabled,
1790
+ href,
1791
+ itemHref,
1792
+ target,
1793
+ density,
1794
+ noDivider,
1795
+ imgHrefImage,
1796
+ avatarLabel,
1797
+ avatarAlt,
1798
+ avatarVariant,
1799
+ avatarSrc,
1800
+ };
1801
+ }),
1802
+ };
1803
+ });
1804
+
1805
+ // Alla scb-link på sidan
1806
+ const linksState = Array.from(links).map((link) => {
1807
+ const anyLink = link;
1808
+ const href = (
1809
+ anyLink.href ??
1810
+ link.getAttribute('href') ??
1811
+ ''
1812
+ ).toString();
1813
+ const target = (
1814
+ anyLink.target ??
1815
+ link.getAttribute('target') ??
1816
+ ''
1817
+ ).toString();
1818
+ const rel = (
1819
+ anyLink.rel ??
1820
+ link.getAttribute('rel') ??
1821
+ ''
1822
+ ).toString();
1823
+ const download = (
1824
+ anyLink.download ??
1825
+ link.getAttribute('download') ??
1826
+ ''
1827
+ ).toString();
1828
+ const disabled =
1829
+ typeof anyLink.disabled === 'boolean'
1830
+ ? anyLink.disabled
1831
+ : link.hasAttribute('disabled');
1832
+ const ariaLabel = (
1833
+ anyLink.ariaLabel ??
1834
+ link.getAttribute('aria-label') ??
1835
+ ''
1836
+ ).toString();
1837
+ const text = (
1838
+ anyLink.text ??
1839
+ (link.textContent ?? '') ??
1840
+ ''
1841
+ ).toString().trim();
1842
+
1843
+ return {
1844
+ href,
1845
+ target,
1846
+ rel,
1847
+ download,
1848
+ disabled: !!disabled,
1849
+ ariaLabel,
1850
+ text,
1851
+ };
1852
+ });
1853
+
1854
+ // Alla scb-sub-menu på sidan (till SubMenus i C#-modellen)
1855
+ const subMenusState = Array.from(subMenus).map((sm) => {
1856
+ const anySm = sm;
1857
+
1858
+ const open =
1859
+ typeof anySm.open === 'boolean'
1860
+ ? anySm.open
1861
+ : sm.hasAttribute('open');
1862
+
1863
+ const openLeft =
1864
+ typeof anySm.openLeft === 'boolean'
1865
+ ? anySm.openLeft
1866
+ : sm.hasAttribute('open-left');
1867
+
1868
+ return {
1869
+ open: !!open,
1870
+ openLeft: !!openLeft,
1871
+ };
1872
+ });
1873
+
1874
+
1875
+ // Objektet nedan är det som serialiseras till C#-modellen ScbStateDto
1876
+ return {
1877
+ header: headerState,
1878
+ drawer: drawerState,
1879
+ breadcrumb: breadcrumbState,
1880
+ breadcrumbItems: breadcrumbItemsState,
1881
+ accordions: accordionsState,
1882
+ checkboxes: checkboxesState,
1883
+ switches: switchesState,
1884
+ appBar: appBarState,
1885
+ segmented: segmentedState,
1886
+ tabs: tabsState,
1887
+ calendar: calendarState,
1888
+ calendars: calendarsState,
1889
+ cards: cardsState,
1890
+ keyfigures: keyfiguresState,
1891
+ menu: menuState,
1892
+ notifications: notificationsState,
1893
+ snackbars: snackbarsState,
1894
+ statusPills: statusPillsState,
1895
+ tooltips: tooltipsState,
1896
+ dialog: dialogState,
1897
+ radioGroups: radioGroupsState,
1898
+ paginations: paginationsState,
1899
+ stepper: stepperState,
1900
+ textfields: textfieldsState,
1901
+ searches: searchesState,
1902
+ tocs: tocsState,
1903
+ viz: vizState,
1904
+ lists: listsState,
1905
+ links: linksState,
1906
+ subMenus: subMenusState,
1907
+ };
1908
+ };
1909
+
1910
+ /* Registrerar globala listeners för normaliserade events och anropar den Blazor-komponent som skickat in dotNetRef. */
1911
+ window.SCBBlazor.registerScbEventHandlers = function (dotNetRef) {
1912
+ if (!dotNetRef || window.__scbBlazorHandlersRegistered) return;
1913
+ window.__scbBlazorHandlersRegistered = true;
1914
+
1915
+ // Lista över normaliserade events som ska trigga uppdatering av Blazor state
1916
+ const relevantEvents = [
1917
+ 'tabchange',
1918
+ 'draweropen',
1919
+ 'search',
1920
+ 'openchange',
1921
+ 'checkedchange',
1922
+ 'selectedchange',
1923
+ 'tabschange',
1924
+ 'titlechange',
1925
+ 'positionchange',
1926
+ 'typechange',
1927
+ 'searchsupportingtextchange',
1928
+ // segmented button
1929
+ 'valueschange',
1930
+ // calendar-card mm
1931
+ 'subtitlechange',
1932
+ 'supportingtextchange',
1933
+ 'variantchange',
1934
+ 'showmediachange',
1935
+ // keyfigure-card
1936
+ 'keyfigurechange',
1937
+ 'cardhrefchange',
1938
+ 'unitchange',
1939
+ // status-pill
1940
+ 'statuschange',
1941
+ 'labelchange',
1942
+ 'showiconchange',
1943
+ // breadcrumb
1944
+ 'showallchange',
1945
+ 'currentchange',
1946
+ // pagination
1947
+ 'pagechange',
1948
+ // stepper
1949
+ 'stepchange',
1950
+ // dialog extra
1951
+ 'iconchange',
1952
+ 'okbuttonchange',
1953
+ 'cancelbuttonchange',
1954
+ 'deletebuttonchange',
1955
+ 'scrimclosechange',
1956
+ // notification extra
1957
+ 'directionchange',
1958
+ 'fullheightchange',
1959
+ 'fullwidthchange',
1960
+ 'showclosebuttonchange',
1961
+ // toc
1962
+ 'detachedchange',
1963
+ 'expandedchange',
1964
+ 'dividerchange',
1965
+ 'itemhrefchange',
1966
+ // viz
1967
+ 'descriptionchange',
1968
+ 'commentchange',
1969
+ 'sourcechange',
1970
+ 'footnotechange',
1971
+ 'langchange',
1972
+ 'imagehrefchange',
1973
+ // värdeförändringar, t.ex. scb-textfield och scb-search
1974
+ 'valuechange',
1975
+ ];
1976
+
1977
+ relevantEvents.forEach((evtName) => {
1978
+ document.addEventListener(evtName, () => {
1979
+ dotNetRef
1980
+ .invokeMethodAsync('OnScbEvent', evtName)
1981
+ .catch((err) =>
1982
+ console.error('SCBBlazor.OnScbEvent failed', err),
1983
+ );
1984
+ });
1985
+ });
1986
+ };
1987
+
1988
+ /*
1989
+ Styrkommandon från Blazor.
1990
+ Dessa funktioner uppdaterar komponenternas state i DOM och triggar normaliserade events
1991
+ så att SCBBlazor.getState och registerScbEventHandlers kan uppdatera Blazor-modellen.
1992
+ */
1993
+ (function () {
1994
+ 'use strict';
1995
+
1996
+ function buildAttrSelector(attr, value) {
1997
+ const v = String(value ?? '')
1998
+ .replace(/\\/g, '\\\\')
1999
+ .replace(/"/g, '\\"');
2000
+ return `[${attr}="${v}"]`;
2001
+ }
2002
+
2003
+ function getOpenState(el) {
2004
+ if (!el) return false;
2005
+ try {
2006
+ if (typeof el.open === 'boolean') return el.open;
2007
+ } catch (_) {}
2008
+ return el.hasAttribute && el.hasAttribute('open');
2009
+ }
2010
+
2011
+ function safeClick(el) {
2012
+ if (!el) return false;
2013
+ try {
2014
+ el.dispatchEvent(
2015
+ new MouseEvent('click', { bubbles: true, composed: true }),
2016
+ );
2017
+ return true;
2018
+ } catch (_) {}
2019
+ try {
2020
+ el.click();
2021
+ return true;
2022
+ } catch (_) {}
2023
+ return false;
2024
+ }
2025
+
2026
+ function findTrigger(kind, id, wantOpen) {
2027
+ if (!id) return null;
2028
+ const selectors = [];
2029
+ if (wantOpen) selectors.push(buildAttrSelector(`data-${kind}-open`, id));
2030
+ else selectors.push(buildAttrSelector(`data-${kind}-close`, id));
2031
+ selectors.push(buildAttrSelector(`data-${kind}-toggle`, id));
2032
+ selectors.push(buildAttrSelector('aria-controls', id));
2033
+ return document.querySelector(selectors.join(','));
2034
+ }
2035
+
2036
+ function getByIndex(selector, index) {
2037
+ const list = document.querySelectorAll(selector);
2038
+ if (!list || list.length === 0) return null;
2039
+ const i = typeof index === 'number' ? index : parseInt(index, 10);
2040
+ if (Number.isNaN(i) || i < 0 || i >= list.length) return null;
2041
+ return list[i];
2042
+ }
2043
+
2044
+ function getById(id, selector) {
2045
+ if (!id) return null;
2046
+ const el = document.getElementById(String(id));
2047
+ if (!el) return null;
2048
+ if (selector && (!(el instanceof Element) || !el.matches(selector))) return null;
2049
+ return el;
2050
+ }
2051
+
2052
+ function toBool(value) {
2053
+ return !!value;
2054
+ }
2055
+
2056
+ function dispatchChange(el, name, detail) {
2057
+ if (!el) return;
2058
+ el.dispatchEvent(
2059
+ new CustomEvent(name, {
2060
+ bubbles: true,
2061
+ composed: true,
2062
+ detail: detail || {},
2063
+ }),
2064
+ );
2065
+ }
2066
+
2067
+ const api = (window.SCBBlazor = window.SCBBlazor || {});
2068
+
2069
+ // Header
2070
+
2071
+ api.setHeaderActiveTab = function (index) {
2072
+ const header = document.querySelector('scb-header');
2073
+ if (!header) return;
2074
+ const i = typeof index === 'number' ? index : parseInt(index, 10) || 0;
2075
+ try {
2076
+ header.activeTab = i;
2077
+ } catch (_) {}
2078
+ header.setAttribute('active-tab', String(i));
2079
+ dispatchChange(header, 'tabchange', { index: i });
2080
+ };
2081
+
2082
+ api.setHeaderDrawerOpen = function (open) {
2083
+ const header = document.querySelector('scb-header');
2084
+ if (!header) return;
2085
+ const isOpen = toBool(open);
2086
+
2087
+ // Markör på hosten används av getState för Header.DrawerOpen
2088
+ try {
2089
+ mirrorHeaderDrawer(header, isOpen);
2090
+ } catch (_) {
2091
+ if (isOpen) header.setAttribute('drawer-open', '');
2092
+ else header.removeAttribute('drawer-open');
2093
+ }
2094
+
2095
+ // Styr drawern som ligger inuti headerns shadow DOM (standard id: main-drawer)
2096
+ try {
2097
+ const sr = header.shadowRoot;
2098
+ if (sr) {
2099
+ const btn =
2100
+ sr.querySelector('.menu-trigger') ||
2101
+ sr.querySelector('[data-drawer-toggle]') ||
2102
+ sr.querySelector('[aria-controls]');
2103
+
2104
+ const drawerId =
2105
+ (btn &&
2106
+ (btn.getAttribute('aria-controls') ||
2107
+ btn.getAttribute('data-drawer-toggle'))) ||
2108
+ 'main-drawer';
2109
+
2110
+ const safeId =
2111
+ window.CSS && typeof window.CSS.escape === 'function'
2112
+ ? window.CSS.escape(String(drawerId))
2113
+ : String(drawerId);
2114
+
2115
+ const drawer =
2116
+ sr.querySelector(`#${safeId}`) ||
2117
+ sr.querySelector('scb-drawer');
2118
+
2119
+ if (drawer) {
2120
+ try {
2121
+ drawer.open = isOpen;
2122
+ } catch (_) {}
2123
+ if (isOpen) drawer.setAttribute('open', '');
2124
+ else drawer.removeAttribute('open');
2125
+ }
2126
+ }
2127
+ } catch (_) {}
2128
+
2129
+ // Trigga state-refresh i Blazor
2130
+ dispatchChange(header, 'draweropen', { open: isOpen });
2131
+ };
2132
+
2133
+ api.setHeaderSearchText = function (text) {
2134
+ const header = document.querySelector('scb-header');
2135
+ if (!header) return;
2136
+ const value = text == null ? '' : String(text);
2137
+ try {
2138
+ header.searchText = value;
2139
+ } catch (_) {}
2140
+ if (value) header.setAttribute('search-text', value);
2141
+ else header.removeAttribute('search-text');
2142
+ dispatchChange(header, 'search', { value });
2143
+ };
2144
+
2145
+ // Drawer och menu
2146
+
2147
+ api.setDrawerOpen = function (open) {
2148
+ const drawer = document.querySelector('scb-drawer');
2149
+ if (!drawer) return;
2150
+
2151
+ const isOpen = toBool(open);
2152
+ const prev = getOpenState(drawer);
2153
+ if (prev === isOpen) return;
2154
+
2155
+ // Primärt: använd samma trigger-logik som i UI:t (aria-controls / data-drawer-*)
2156
+ const id = drawer.id;
2157
+ const trigger = findTrigger('drawer', id, isOpen);
2158
+ if (trigger) {
2159
+ safeClick(trigger);
2160
+ if (getOpenState(drawer) === isOpen) return;
2161
+ }
2162
+
2163
+ // Fallback: sätt open direkt
2164
+ try {
2165
+ drawer.open = isOpen;
2166
+ } catch (_) {}
2167
+ if (isOpen) drawer.setAttribute('open', '');
2168
+ else drawer.removeAttribute('open');
2169
+ };
2170
+
2171
+ api.setDrawerText = function (label, subLabel) {
2172
+ const drawer = document.querySelector('scb-drawer');
2173
+ if (!drawer) return;
2174
+ const l = label == null ? '' : String(label);
2175
+ const s = subLabel == null ? '' : String(subLabel);
2176
+ drawer.setAttribute('label', l);
2177
+ drawer.setAttribute('sub-label', s);
2178
+ };
2179
+
2180
+ api.setMenuOpen = function (open) {
2181
+ const menu = document.querySelector('scb-menu');
2182
+ if (!menu) return;
2183
+ const isOpen = toBool(open);
2184
+ try {
2185
+ menu.open = isOpen;
2186
+ } catch (_) {}
2187
+ if (isOpen) menu.setAttribute('open', '');
2188
+ else menu.removeAttribute('open');
2189
+ dispatchChange(menu, 'openchange', { open: isOpen });
2190
+ };
2191
+
2192
+ api.setSubMenuOpen = function (index, open) {
2193
+ const subMenu = getByIndex('scb-sub-menu', index);
2194
+ if (!subMenu) return;
2195
+ const isOpen = toBool(open);
2196
+ try {
2197
+ subMenu.open = isOpen;
2198
+ } catch (_) {}
2199
+ if (isOpen) subMenu.setAttribute('open', '');
2200
+ else subMenu.removeAttribute('open');
2201
+ dispatchChange(subMenu, 'openchange', { open: isOpen });
2202
+ };
2203
+
2204
+ api.setSubMenuOpenById = function (id, open) {
2205
+ const subMenu = getById(id, 'scb-sub-menu');
2206
+ if (!subMenu) return;
2207
+ const isOpen = toBool(open);
2208
+ try {
2209
+ subMenu.open = isOpen;
2210
+ } catch (_) {}
2211
+ if (isOpen) subMenu.setAttribute('open', '');
2212
+ else subMenu.removeAttribute('open');
2213
+ dispatchChange(subMenu, 'openchange', { open: isOpen });
2214
+ };
2215
+
2216
+ // Breadcrumb
2217
+
2218
+ api.setBreadcrumbShowAll = function (showAll) {
2219
+ const breadcrumb = document.querySelector('scb-breadcrumb');
2220
+ if (!breadcrumb) return;
2221
+ const value = toBool(showAll);
2222
+ try {
2223
+ breadcrumb.showAll = value;
2224
+ } catch (_) {}
2225
+ if (value) breadcrumb.setAttribute('show-all', '');
2226
+ else breadcrumb.removeAttribute('show-all');
2227
+ dispatchChange(breadcrumb, 'showallchange', { value });
2228
+ };
2229
+
2230
+ // Accordion
2231
+
2232
+ api.setAccordionItemOpen = function (accordionIndex, itemIndex, open) {
2233
+ const acc = getByIndex('scb-accordion', accordionIndex);
2234
+ if (!acc) return;
2235
+ const items = acc.querySelectorAll('scb-accordion-item');
2236
+ if (!items || items.length === 0) return;
2237
+ const i = typeof itemIndex === 'number' ? itemIndex : parseInt(itemIndex, 10) || 0;
2238
+ if (i < 0 || i >= items.length) return;
2239
+ const item = items[i];
2240
+ const isOpen = toBool(open);
2241
+ try {
2242
+ item.open = isOpen;
2243
+ } catch (_) {}
2244
+ if (isOpen) item.setAttribute('open', '');
2245
+ else item.removeAttribute('open');
2246
+ dispatchChange(item, 'openchange', { open: isOpen });
2247
+ };
2248
+
2249
+ api.setAccordionItemOpenById = function (itemId, open) {
2250
+ const item = getById(itemId, 'scb-accordion-item');
2251
+ if (!item) return;
2252
+ const isOpen = toBool(open);
2253
+ try {
2254
+ item.open = isOpen;
2255
+ } catch (_) {}
2256
+ if (isOpen) item.setAttribute('open', '');
2257
+ else item.removeAttribute('open');
2258
+ dispatchChange(item, 'openchange', { open: isOpen });
2259
+ };
2260
+
2261
+ // Checkbox och switch
2262
+
2263
+ api.setCheckboxChecked = function (index, checked) {
2264
+ const cb = getByIndex('scb-checkbox', index);
2265
+ if (!cb) return;
2266
+ const isChecked = toBool(checked);
2267
+ try {
2268
+ cb.checked = isChecked;
2269
+ } catch (_) {}
2270
+ if (isChecked) cb.setAttribute('checked', '');
2271
+ else cb.removeAttribute('checked');
2272
+ dispatchChange(cb, 'checkedchange', { checked: isChecked });
2273
+ };
2274
+
2275
+ api.setCheckboxCheckedById = function (id, checked) {
2276
+ const cb = getById(id, 'scb-checkbox');
2277
+ if (!cb) return;
2278
+ const isChecked = toBool(checked);
2279
+ try {
2280
+ cb.checked = isChecked;
2281
+ } catch (_) {}
2282
+ if (isChecked) cb.setAttribute('checked', '');
2283
+ else cb.removeAttribute('checked');
2284
+ dispatchChange(cb, 'checkedchange', { checked: isChecked });
2285
+ };
2286
+
2287
+ api.setSwitchSelected = function (index, selected) {
2288
+ const sw = getByIndex('scb-switch', index);
2289
+ if (!sw) return;
2290
+ const isSelected = toBool(selected);
2291
+ try {
2292
+ sw.selected = isSelected;
2293
+ } catch (_) {}
2294
+ if (isSelected) sw.setAttribute('selected', '');
2295
+ else sw.removeAttribute('selected');
2296
+ dispatchChange(sw, 'selectedchange', { selected: isSelected });
2297
+ };
2298
+
2299
+ api.setSwitchSelectedById = function (id, selected) {
2300
+ const sw = getById(id, 'scb-switch');
2301
+ if (!sw) return;
2302
+ const isSelected = toBool(selected);
2303
+ try {
2304
+ sw.selected = isSelected;
2305
+ } catch (_) {}
2306
+ if (isSelected) sw.setAttribute('selected', '');
2307
+ else sw.removeAttribute('selected');
2308
+ dispatchChange(sw, 'selectedchange', { selected: isSelected });
2309
+ };
2310
+
2311
+ // Segmented button
2312
+
2313
+ api.setSegmentedValue = function (value) {
2314
+ const seg = document.querySelector('scb-segmented-button');
2315
+ if (!seg) return;
2316
+ const v = value == null ? '' : String(value);
2317
+ try {
2318
+ seg.value = v;
2319
+ } catch (_) {}
2320
+ if (v) seg.setAttribute('value', v);
2321
+ else seg.removeAttribute('value');
2322
+ dispatchChange(seg, 'valuechange', { value: v });
2323
+ };
2324
+
2325
+ api.setSegmentedValues = function (values) {
2326
+ const seg = document.querySelector('scb-segmented-button');
2327
+ if (!seg) return;
2328
+ const arr = Array.isArray(values) ? values.map((v) => String(v)) : [];
2329
+ try {
2330
+ seg.values = arr;
2331
+ } catch (_) {}
2332
+ if (arr.length > 0) {
2333
+ seg.setAttribute('value', arr[0]);
2334
+ } else {
2335
+ seg.removeAttribute('value');
2336
+ }
2337
+ dispatchChange(seg, 'valueschange', { values: arr });
2338
+ };
2339
+
2340
+ api.setSegmentedValueById = function (id, value) {
2341
+ const seg = getById(id, 'scb-segmented-button');
2342
+ if (!seg) return;
2343
+ const v = value == null ? '' : String(value);
2344
+ try {
2345
+ seg.value = v;
2346
+ } catch (_) {}
2347
+ if (v) seg.setAttribute('value', v);
2348
+ else seg.removeAttribute('value');
2349
+ dispatchChange(seg, 'valuechange', { value: v });
2350
+ };
2351
+
2352
+ api.setSegmentedValuesById = function (id, values) {
2353
+ const seg = getById(id, 'scb-segmented-button');
2354
+ if (!seg) return;
2355
+ const arr = Array.isArray(values) ? values.map((v) => String(v)) : [];
2356
+ try {
2357
+ seg.values = arr;
2358
+ } catch (_) {}
2359
+ if (arr.length > 0) {
2360
+ seg.setAttribute('value', arr[0]);
2361
+ } else {
2362
+ seg.removeAttribute('value');
2363
+ }
2364
+ dispatchChange(seg, 'valueschange', { values: arr });
2365
+ };
2366
+
2367
+ // Tabs
2368
+
2369
+ api.setTabsActiveIndex = function (tabsIndex, activeIndex) {
2370
+ const tabs = getByIndex('scb-tabs', tabsIndex);
2371
+ if (!tabs) return;
2372
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2373
+ const anyTabs = tabs;
2374
+ try {
2375
+ anyTabs.activeTabIndex = i;
2376
+ } catch (_) {}
2377
+ tabs.setAttribute('active-tab-index', String(i));
2378
+ dispatchChange(tabs, 'tabschange', { activeIndex: i });
2379
+ };
2380
+
2381
+ api.setTabsActiveIndexById = function (id, activeIndex) {
2382
+ const tabs = getById(id, 'scb-tabs');
2383
+ if (!tabs) return;
2384
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2385
+ const anyTabs = tabs;
2386
+ try {
2387
+ anyTabs.activeTabIndex = i;
2388
+ } catch (_) {}
2389
+ tabs.setAttribute('active-tab-index', String(i));
2390
+ dispatchChange(tabs, 'tabschange', { activeIndex: i });
2391
+ };
2392
+
2393
+ // Dialog, notification och snackbar
2394
+
2395
+ api.setDialogOpen = function (open) {
2396
+ const dialog = document.querySelector('scb-dialog');
2397
+ if (!dialog) return;
2398
+
2399
+ const isOpen = toBool(open);
2400
+ const prev = getOpenState(dialog);
2401
+ if (prev === isOpen) return;
2402
+
2403
+ // Primärt: använd samma trigger-logik som i UI:t (aria-controls / data-dialog-*)
2404
+ const id = dialog.id;
2405
+ const trigger = findTrigger('dialog', id, isOpen);
2406
+ if (trigger) {
2407
+ safeClick(trigger);
2408
+ if (getOpenState(dialog) === isOpen) {
2409
+ // Dialogen behöver en normaliserad event-signal för att Blazor ska uppdatera state
2410
+ dispatchChange(dialog, 'openchange', { open: isOpen });
2411
+ return;
2412
+ }
2413
+ }
2414
+
2415
+ // Fallback: försök komponentens API om det finns
2416
+ try {
2417
+ if (isOpen && typeof dialog.showModal === 'function') dialog.showModal();
2418
+ else if (isOpen && typeof dialog.show === 'function') dialog.show();
2419
+ else if (!isOpen && typeof dialog.close === 'function') dialog.close();
2420
+ } catch (_) {}
2421
+
2422
+ // Sista fallback: sätt open direkt
2423
+ try {
2424
+ dialog.open = isOpen;
2425
+ } catch (_) {}
2426
+ if (isOpen) dialog.setAttribute('open', '');
2427
+ else dialog.removeAttribute('open');
2428
+
2429
+ dispatchChange(dialog, 'openchange', { open: isOpen });
2430
+ };
2431
+
2432
+ api.setNotificationOpen = function (index, open) {
2433
+ const card = getByIndex('scb-notification-card', index);
2434
+ if (!card) return;
2435
+ const isOpen = toBool(open);
2436
+ if (isOpen) card.setAttribute('open', '');
2437
+ else card.removeAttribute('open');
2438
+ dispatchChange(card, 'openchange', { open: isOpen });
2439
+ };
2440
+
2441
+ api.setNotificationOpenById = function (id, open) {
2442
+ const card = getById(id, 'scb-notification-card');
2443
+ if (!card) return;
2444
+ const isOpen = toBool(open);
2445
+ if (isOpen) card.setAttribute('open', '');
2446
+ else card.removeAttribute('open');
2447
+ dispatchChange(card, 'openchange', { open: isOpen });
2448
+ };
2449
+
2450
+ api.setSnackbarOpen = function (index, open) {
2451
+ const sb = getByIndex('scb-snackbar', index);
2452
+ if (!sb) return;
2453
+ const isOpen = toBool(open);
2454
+ if (isOpen) sb.setAttribute('open', '');
2455
+ else sb.removeAttribute('open');
2456
+ dispatchChange(sb, 'openchange', { open: isOpen });
2457
+ };
2458
+
2459
+ api.setSnackbarOpenById = function (id, open) {
2460
+ const sb = getById(id, 'scb-snackbar');
2461
+ if (!sb) return;
2462
+ const isOpen = toBool(open);
2463
+ if (isOpen) sb.setAttribute('open', '');
2464
+ else sb.removeAttribute('open');
2465
+ dispatchChange(sb, 'openchange', { open: isOpen });
2466
+ };
2467
+
2468
+ // Stepper och pagination
2469
+
2470
+ api.setStepperActiveIndex = function (activeIndex) {
2471
+ const stepper = document.querySelector('scb-stepper');
2472
+ if (!stepper) return;
2473
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2474
+ const anyStepper = stepper;
2475
+ try {
2476
+ anyStepper.activeIndex = i;
2477
+ } catch (_) {}
2478
+ stepper.setAttribute('active-index', String(i));
2479
+ dispatchChange(stepper, 'stepchange', { activeIndex: i });
2480
+ };
2481
+
2482
+ api.setStepperActiveIndexById = function (id, activeIndex) {
2483
+ const stepper = getById(id, 'scb-stepper');
2484
+ if (!stepper) return;
2485
+ const i = typeof activeIndex === 'number' ? activeIndex : parseInt(activeIndex, 10) || 0;
2486
+ const anyStepper = stepper;
2487
+ try {
2488
+ anyStepper.activeIndex = i;
2489
+ } catch (_) {}
2490
+ stepper.setAttribute('active-index', String(i));
2491
+ dispatchChange(stepper, 'stepchange', { activeIndex: i });
2492
+ };
2493
+
2494
+ api.setPaginationPage = function (paginationIndex, page) {
2495
+ const p = getByIndex('scb-pagination', paginationIndex);
2496
+ if (!p) return;
2497
+ const i = typeof page === 'number' ? page : parseInt(page, 10) || 0;
2498
+ const anyP = p;
2499
+ try {
2500
+ anyP.page = i;
2501
+ } catch (_) {}
2502
+ p.setAttribute('page', String(i));
2503
+ dispatchChange(p, 'pagechange', { page: i });
2504
+ };
2505
+
2506
+ api.setPaginationPageById = function (id, page) {
2507
+ const p = getById(id, 'scb-pagination');
2508
+ if (!p) return;
2509
+ const i = typeof page === 'number' ? page : parseInt(page, 10) || 0;
2510
+ const anyP = p;
2511
+ try {
2512
+ anyP.page = i;
2513
+ } catch (_) {}
2514
+ p.setAttribute('page', String(i));
2515
+ dispatchChange(p, 'pagechange', { page: i });
2516
+ };
2517
+
2518
+ // Radio group
2519
+
2520
+ api.setRadioGroupValueByIndex = function (groupIndex, value) {
2521
+ const group = getByIndex('scb-radio-group', groupIndex);
2522
+ if (!group) return;
2523
+ const v = value == null ? '' : String(value);
2524
+ try {
2525
+ group.value = v;
2526
+ } catch (_) {}
2527
+ group.setAttribute('value', v);
2528
+ dispatchChange(group, 'valuechange', { value: v });
2529
+ };
2530
+
2531
+ api.setRadioGroupValueByName = function (name, value) {
2532
+ if (!name) return;
2533
+ const selector = 'scb-radio-group[name="' + String(name) + '"]';
2534
+ const group = document.querySelector(selector);
2535
+ if (!group) return;
2536
+ const v = value == null ? '' : String(value);
2537
+ try {
2538
+ group.value = v;
2539
+ } catch (_) {}
2540
+ group.setAttribute('value', v);
2541
+ dispatchChange(group, 'valuechange', { value: v });
2542
+ };
2543
+
2544
+ api.setRadioGroupValueById = function (id, value) {
2545
+ const group = getById(id, 'scb-radio-group');
2546
+ if (!group) return;
2547
+ const v = value == null ? '' : String(value);
2548
+ try {
2549
+ group.value = v;
2550
+ } catch (_) {}
2551
+ group.setAttribute('value', v);
2552
+ dispatchChange(group, 'valuechange', { value: v });
2553
+ };
2554
+
2555
+ // Textfield och search
2556
+
2557
+ api.setTextfieldValue = function (index, value) {
2558
+ const tf = getByIndex('scb-textfield', index);
2559
+ if (!tf) return;
2560
+ const v = value == null ? '' : String(value);
2561
+ const anyTf = tf;
2562
+ try {
2563
+ anyTf.value = v;
2564
+ } catch (_) {}
2565
+ tf.setAttribute('value', v);
2566
+ dispatchChange(tf, 'valuechange', { value: v });
2567
+ };
2568
+
2569
+ api.setTextfieldValueById = function (id, value) {
2570
+ const tf = getById(id, 'scb-textfield');
2571
+ if (!tf) return;
2572
+ const v = value == null ? '' : String(value);
2573
+ const anyTf = tf;
2574
+ try {
2575
+ anyTf.value = v;
2576
+ } catch (_) {}
2577
+ tf.setAttribute('value', v);
2578
+ dispatchChange(tf, 'valuechange', { value: v });
2579
+ };
2580
+
2581
+ api.setSearchValue = function (index, value) {
2582
+ const s = getByIndex('scb-search', index);
2583
+ if (!s) return;
2584
+ const v = value == null ? '' : String(value);
2585
+ const anyS = s;
2586
+ try {
2587
+ anyS.value = v;
2588
+ } catch (_) {}
2589
+ s.setAttribute('value', v);
2590
+ dispatchChange(s, 'valuechange', { value: v });
2591
+ };
2592
+
2593
+ api.setSearchValueById = function (id, value) {
2594
+ const s = getById(id, 'scb-search');
2595
+ if (!s) return;
2596
+ const v = value == null ? '' : String(value);
2597
+ const anyS = s;
2598
+ try {
2599
+ anyS.value = v;
2600
+ } catch (_) {}
2601
+ s.setAttribute('value', v);
2602
+ dispatchChange(s, 'valuechange', { value: v });
2603
+ };
2604
+
2605
+ // TOC
2606
+
2607
+ api.setTocItemExpanded = function (tocIndex, itemIndex, expanded) {
2608
+ const toc = getByIndex('scb-toc', tocIndex);
2609
+ if (!toc) return;
2610
+ const items = toc.querySelectorAll('scb-toc-item');
2611
+ if (!items || items.length === 0) return;
2612
+ const i = typeof itemIndex === 'number' ? itemIndex : parseInt(itemIndex, 10) || 0;
2613
+ if (i < 0 || i >= items.length) return;
2614
+ const item = items[i];
2615
+ const isExpanded = toBool(expanded);
2616
+ const anyItem = item;
2617
+ try {
2618
+ anyItem.expanded = isExpanded;
2619
+ } catch (_) {}
2620
+ if (isExpanded) item.setAttribute('expanded', '');
2621
+ else item.removeAttribute('expanded');
2622
+ dispatchChange(item, 'expandedchange', { expanded: isExpanded });
2623
+ };
2624
+
2625
+ api.setTocItemExpandedById = function (itemId, expanded) {
2626
+ const item = getById(itemId, 'scb-toc-item');
2627
+ if (!item) return;
2628
+ const isExpanded = toBool(expanded);
2629
+ const anyItem = item;
2630
+ try {
2631
+ anyItem.expanded = isExpanded;
2632
+ } catch (_) {}
2633
+ if (isExpanded) item.setAttribute('expanded', '');
2634
+ else item.removeAttribute('expanded');
2635
+ dispatchChange(item, 'expandedchange', { expanded: isExpanded });
2636
+ };
2637
+
2638
+ // Viz
2639
+
2640
+ api.setVizSelectedChip = function (index, selectedChip) {
2641
+ const viz = getByIndex('scb-viz', index);
2642
+ if (!viz) return;
2643
+ const value = selectedChip == null ? '' : String(selectedChip);
2644
+ const anyViz = viz;
2645
+ try {
2646
+ anyViz.selectedChip = value;
2647
+ } catch (_) {}
2648
+ dispatchChange(viz, 'valuechange', { value });
2649
+ };
2650
+
2651
+ api.setVizSelectedChipById = function (id, selectedChip) {
2652
+ const viz = getById(id, 'scb-viz');
2653
+ if (!viz) return;
2654
+ const value = selectedChip == null ? '' : String(selectedChip);
2655
+ const anyViz = viz;
2656
+ try {
2657
+ anyViz.selectedChip = value;
2658
+ } catch (_) {}
2659
+ dispatchChange(viz, 'valuechange', { value });
2660
+ };
2661
+
2662
+ api.setVizView = function (index, view) {
2663
+ const viz = getByIndex('scb-viz', index);
2664
+ if (!viz) return;
2665
+ setVizViewOnElement(viz, view);
2666
+ };
2667
+
2668
+ api.setVizViewById = function (id, view) {
2669
+ const viz = getById(id, 'scb-viz');
2670
+ if (!viz) return;
2671
+ setVizViewOnElement(viz, view);
2672
+ };
2673
+
2674
+ function setVizViewOnElement(viz, view) {
2675
+ const raw = view == null ? '' : String(view);
2676
+ const v = raw.trim().toLowerCase();
2677
+ const showTable = v === 'table' || v === 'tabell';
2678
+
2679
+ // Försöker först styra via viz egna UI (segmented) så att både vy och vald knapp hamnar i synk.
2680
+ try {
2681
+ const sr = viz.shadowRoot;
2682
+ if (sr) {
2683
+ const scbSegmented = sr.querySelector('scb-segmented-button');
2684
+ if (scbSegmented) {
2685
+ const targetLabel = showTable ? 'tabell' : 'diagram';
2686
+ const targetLabelAlt = showTable ? 'table' : 'chart';
2687
+
2688
+ const items = Array.from(
2689
+ scbSegmented.querySelectorAll('scb-segmented-item, button, [role="button"]'),
2690
+ );
2691
+
2692
+ const pickByLabel = (el) => {
2693
+ const label = (
2694
+ el.getAttribute('label') ||
2695
+ el.getAttribute('aria-label') ||
2696
+ (el.label ?? '')
2697
+ )
2698
+ .toString()
2699
+ .trim()
2700
+ .toLowerCase();
2701
+ const text = (el.textContent ?? '').toString().trim().toLowerCase();
2702
+ const s = `${label} ${text}`.trim();
2703
+ return s.includes(targetLabel) || s.includes(targetLabelAlt);
2704
+ };
2705
+
2706
+ let targetEl = items.find(pickByLabel);
2707
+ if (!targetEl && items.length >= 2) {
2708
+ targetEl = items[showTable ? 1 : 0];
2709
+ }
2710
+
2711
+ if (targetEl) {
2712
+ const itemValue = (
2713
+ targetEl.getAttribute('value') ||
2714
+ targetEl.getAttribute('data-value') ||
2715
+ (targetEl.value ?? '')
2716
+ )
2717
+ .toString()
2718
+ .trim();
2719
+
2720
+ if (itemValue) {
2721
+ try {
2722
+ scbSegmented.value = itemValue;
2723
+ } catch (_) {}
2724
+ }
2725
+
2726
+ if (typeof targetEl.click === 'function') {
2727
+ targetEl.click();
2728
+ }
2729
+
2730
+ return;
2731
+ }
2732
+ }
2733
+
2734
+ // Fallback för Material/Web eller annan intern implementation där knapparna ligger direkt i shadowRoot.
2735
+ const mdButtons = Array.from(sr.querySelectorAll('md-segmented-button'));
2736
+ const buttons = mdButtons.length
2737
+ ? mdButtons
2738
+ : Array.from(sr.querySelectorAll('button, [role="button"]'));
2739
+
2740
+ if (buttons.length) {
2741
+ const targetLabel = showTable ? 'tabell' : 'diagram';
2742
+ const targetLabelAlt = showTable ? 'table' : 'chart';
2743
+
2744
+ const pickByLabel = (el) => {
2745
+ const label = (
2746
+ el.getAttribute('label') ||
2747
+ el.getAttribute('aria-label') ||
2748
+ (el.label ?? '')
2749
+ )
2750
+ .toString()
2751
+ .trim()
2752
+ .toLowerCase();
2753
+ const text = (el.textContent ?? '').toString().trim().toLowerCase();
2754
+ const s = `${label} ${text}`.trim();
2755
+ return s.includes(targetLabel) || s.includes(targetLabelAlt);
2756
+ };
2757
+
2758
+ let targetEl = buttons.find(pickByLabel);
2759
+ if (!targetEl && buttons.length >= 2) {
2760
+ targetEl = buttons[showTable ? 1 : 0];
2761
+ }
2762
+
2763
+ if (targetEl) {
2764
+ if (typeof targetEl.click === 'function') {
2765
+ targetEl.click();
2766
+ return;
2767
+ }
2768
+
2769
+ try {
2770
+ targetEl.dispatchEvent(
2771
+ new MouseEvent('click', { bubbles: true, composed: true }),
2772
+ );
2773
+ return;
2774
+ } catch (_) {}
2775
+ }
2776
+ }
2777
+ }
2778
+ } catch (_) {}
2779
+
2780
+ // Sista fallback: växlar synlighet på slottat innehåll.
2781
+ const diagramSlot = viz.querySelector('[slot="diagram"]');
2782
+ const tableSlot = viz.querySelector('[slot="table"]');
2783
+
2784
+ if (diagramSlot) {
2785
+ if (showTable) {
2786
+ diagramSlot.setAttribute('hidden', '');
2787
+ diagramSlot.style.setProperty('display', 'none', 'important');
2788
+ } else {
2789
+ diagramSlot.removeAttribute('hidden');
2790
+ diagramSlot.style.removeProperty('display');
2791
+ }
2792
+ }
2793
+
2794
+ if (tableSlot) {
2795
+ if (showTable) {
2796
+ tableSlot.removeAttribute('hidden');
2797
+ tableSlot.style.removeProperty('display');
2798
+ } else {
2799
+ tableSlot.setAttribute('hidden', '');
2800
+ tableSlot.style.setProperty('display', 'none', 'important');
2801
+ }
2802
+ }
2803
+ };
2804
+
2805
+ })();