@c15t/dev-tools 2.0.0-rc.2 → 2.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/panel.d.ts +1 -0
  3. package/dist/components/panel.d.ts.map +1 -1
  4. package/dist/components/tabs.d.ts.map +1 -1
  5. package/dist/components/ui.d.ts +8 -0
  6. package/dist/components/ui.d.ts.map +1 -1
  7. package/dist/core/debug-bundle.d.ts +14 -0
  8. package/dist/core/debug-bundle.d.ts.map +1 -0
  9. package/dist/core/devtools.d.ts.map +1 -1
  10. package/dist/core/override-storage.d.ts +7 -0
  11. package/dist/core/override-storage.d.ts.map +1 -0
  12. package/dist/core/panel-renderer.d.ts +5 -0
  13. package/dist/core/panel-renderer.d.ts.map +1 -1
  14. package/dist/core/state-manager.d.ts +1 -1
  15. package/dist/core/state-manager.d.ts.map +1 -1
  16. package/dist/core/store-connector.d.ts +20 -0
  17. package/dist/core/store-connector.d.ts.map +1 -1
  18. package/dist/core/store-instrumentation.d.ts +13 -0
  19. package/dist/core/store-instrumentation.d.ts.map +1 -0
  20. package/dist/index.cjs +2469 -845
  21. package/dist/index.js +2472 -848
  22. package/dist/panels/actions.d.ts +1 -0
  23. package/dist/panels/actions.d.ts.map +1 -1
  24. package/dist/panels/consents.d.ts.map +1 -1
  25. package/dist/panels/dom-scanner.d.ts.map +1 -1
  26. package/dist/panels/events.d.ts.map +1 -1
  27. package/dist/panels/iab.d.ts +6 -0
  28. package/dist/panels/iab.d.ts.map +1 -1
  29. package/dist/panels/location.d.ts +9 -6
  30. package/dist/panels/location.d.ts.map +1 -1
  31. package/dist/panels/scripts.d.ts +2 -0
  32. package/dist/panels/scripts.d.ts.map +1 -1
  33. package/dist/react.cjs +2392 -752
  34. package/dist/react.js +2376 -736
  35. package/dist/tanstack.cjs +2197 -555
  36. package/dist/tanstack.js +2195 -553
  37. package/dist/utils/preference-trigger.d.ts +2 -2
  38. package/dist/utils/preference-trigger.d.ts.map +1 -1
  39. package/dist/version.d.ts +2 -0
  40. package/dist/version.d.ts.map +1 -0
  41. package/package.json +16 -14
  42. package/tsconfig.json +9 -1
  43. package/dist/__tests__/components/ui.test.d.ts +0 -2
  44. package/dist/__tests__/components/ui.test.d.ts.map +0 -1
  45. package/dist/__tests__/core/renderer.test.d.ts +0 -2
  46. package/dist/__tests__/core/renderer.test.d.ts.map +0 -1
  47. package/dist/__tests__/core/reset-consents.test.d.ts +0 -2
  48. package/dist/__tests__/core/reset-consents.test.d.ts.map +0 -1
  49. package/dist/__tests__/core/state-manager.test.d.ts +0 -2
  50. package/dist/__tests__/core/state-manager.test.d.ts.map +0 -1
  51. package/dist/__tests__/panels/dom-scanner.test.d.ts +0 -2
  52. package/dist/__tests__/panels/dom-scanner.test.d.ts.map +0 -1
package/dist/react.js CHANGED
@@ -261,21 +261,21 @@ var __webpack_modules__ = {
261
261
  module.id,
262
262
  `.toggle-bPZtik {
263
263
  border-radius: var(--c15t-radius-full, 9999px);
264
- background-color: var(--c15t-switch-track, #ccc);
264
+ background-color: var(--c15t-switch-track, #d9d9d9);
265
265
  cursor: pointer;
266
- width: 36px;
267
- height: 20px;
268
- transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
266
+ width: 2rem;
267
+ height: 1.25rem;
268
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), box-shadow var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
269
269
  border: none;
270
270
  align-items: center;
271
- padding: 0;
271
+ padding: .125rem;
272
272
  display: inline-flex;
273
273
  position: relative;
274
274
  }
275
275
 
276
276
  .toggle-bPZtik:focus-visible {
277
- outline: 2px solid var(--c15t-primary, #335cff);
278
- outline-offset: 2px;
277
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
278
+ outline: none;
279
279
  }
280
280
 
281
281
  .toggleActive-Ldlasg {
@@ -285,16 +285,16 @@ var __webpack_modules__ = {
285
285
  .toggleThumb-hjGfoX {
286
286
  border-radius: var(--c15t-radius-full, 9999px);
287
287
  background-color: var(--c15t-switch-thumb, #fff);
288
- width: 16px;
289
- height: 16px;
290
- transition: transform var(--c15t-duration-fast, .1s) var(--c15t-easing-spring, cubic-bezier(.34, 1.56, .64, 1));
288
+ width: .75rem;
289
+ height: .75rem;
290
+ box-shadow: 0 0 0 1px var(--c15t-border, #e3e3e3);
291
+ transition: transform var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
291
292
  position: absolute;
292
293
  left: 2px;
293
- box-shadow: 0 1px 2px #0003;
294
294
  }
295
295
 
296
296
  .toggleActive-Ldlasg .toggleThumb-hjGfoX {
297
- transform: translateX(16px);
297
+ transform: translateX(1rem);
298
298
  }
299
299
 
300
300
  .toggle-bPZtik:disabled, .toggleDisabled-ZcD8nZ {
@@ -307,13 +307,14 @@ var __webpack_modules__ = {
307
307
  }
308
308
 
309
309
  .badge-yA0giZ {
310
- border-radius: var(--c15t-radius-sm, .25rem);
310
+ border-radius: var(--c15t-radius-full, 9999px);
311
311
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
312
312
  font-weight: var(--c15t-font-weight-medium, 500);
313
+ line-height: var(--c15t-line-height-tight, 1.25);
313
314
  white-space: nowrap;
315
+ justify-content: center;
314
316
  align-items: center;
315
- padding: 2px 6px;
316
- line-height: 1;
317
+ padding: .1875rem .4375rem;
317
318
  display: inline-flex;
318
319
  }
319
320
 
@@ -346,38 +347,49 @@ var __webpack_modules__ = {
346
347
  justify-content: center;
347
348
  align-items: center;
348
349
  gap: var(--c15t-space-xs, .25rem);
349
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
350
- border: 1px solid var(--c15t-border, #e3e3e3);
350
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
351
351
  border-radius: var(--c15t-radius-md, .5rem);
352
- background-color: var(--c15t-surface, #fff);
352
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
353
+ min-height: 2rem;
353
354
  color: var(--c15t-text, #171717);
354
- font-family: inherit;
355
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
355
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
356
+ font-size: var(--c15t-font-size-sm, .875rem);
356
357
  font-weight: var(--c15t-font-weight-medium, 500);
358
+ line-height: var(--c15t-line-height-tight, 1.25);
359
+ box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000000d);
357
360
  cursor: pointer;
358
- transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
361
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), box-shadow var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
362
+ padding: .375rem .625rem;
359
363
  display: inline-flex;
360
364
  }
361
365
 
362
366
  .btn-evRVlh:hover {
363
- background-color: var(--c15t-surface-hover, #f7f7f7);
367
+ background-color: var(--c15t-devtools-surface-subtle, #f7f7f7);
364
368
  border-color: var(--c15t-border-hover, #c9c9c9);
369
+ box-shadow: var(--c15t-shadow-md, 0 4px 12px #00000014);
365
370
  }
366
371
 
367
372
  .btn-evRVlh:focus-visible {
368
- outline: 2px solid var(--c15t-primary, #335cff);
369
- outline-offset: 1px;
373
+ box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000000d), 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
374
+ outline: none;
375
+ }
376
+
377
+ .btn-evRVlh:active {
378
+ box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000000d);
379
+ transform: scale(.98);
370
380
  }
371
381
 
372
382
  .btn-evRVlh:disabled {
373
383
  opacity: .5;
374
384
  cursor: not-allowed;
385
+ box-shadow: none;
375
386
  }
376
387
 
377
388
  .btnPrimary-dA6nqY {
378
389
  background-color: var(--c15t-primary, #335cff);
379
390
  border-color: var(--c15t-primary, #335cff);
380
391
  color: var(--c15t-text-on-primary, #fff);
392
+ box-shadow: none;
381
393
  }
382
394
 
383
395
  .btnPrimary-dA6nqY:hover {
@@ -389,6 +401,7 @@ var __webpack_modules__ = {
389
401
  background-color: var(--c15t-devtools-badge-error, #ef4343);
390
402
  border-color: var(--c15t-devtools-badge-error, #ef4343);
391
403
  color: var(--c15t-text-on-primary, #fff);
404
+ box-shadow: none;
392
405
  }
393
406
 
394
407
  .btnDanger-eDnqOX:hover {
@@ -397,8 +410,10 @@ var __webpack_modules__ = {
397
410
  }
398
411
 
399
412
  .btnSmall-TjXoqZ {
400
- padding: 2px var(--c15t-space-xs, .25rem);
401
- font-size: 10px;
413
+ min-height: 1.75rem;
414
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
415
+ border-radius: var(--c15t-radius-sm, .375rem);
416
+ padding: .25rem .5rem;
402
417
  }
403
418
 
404
419
  .btnIcon-fiYQAh {
@@ -408,19 +423,22 @@ var __webpack_modules__ = {
408
423
  }
409
424
 
410
425
  .input-IeTcCs {
411
- width: 100%;
412
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
413
- border: 1px solid var(--c15t-border, #e3e3e3);
426
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
414
427
  border-radius: var(--c15t-radius-md, .5rem);
415
- background-color: var(--c15t-surface, #fff);
428
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
429
+ width: 100%;
430
+ min-height: 2rem;
416
431
  color: var(--c15t-text, #171717);
417
- font-family: inherit;
418
- font-size: var(--c15t-font-size-sm, .875rem);
419
- transition: border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
432
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
433
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
434
+ line-height: var(--c15t-line-height-tight, 1.25);
435
+ transition: border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), box-shadow var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
436
+ padding: .375rem .625rem;
420
437
  }
421
438
 
422
439
  .input-IeTcCs:focus {
423
- border-color: var(--c15t-primary, #335cff);
440
+ border-color: var(--c15t-devtools-focus-ring, #335cff);
441
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--c15t-devtools-focus-ring, #335cff) 25%, transparent);
424
442
  outline: none;
425
443
  }
426
444
 
@@ -429,30 +447,35 @@ var __webpack_modules__ = {
429
447
  }
430
448
 
431
449
  .inputSmall-pJyXcL {
432
- padding: 2px var(--c15t-space-xs, .25rem);
450
+ min-height: 1.625rem;
433
451
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
452
+ padding: .25rem .4375rem;
434
453
  }
435
454
 
436
455
  .select-byJ1WM {
437
- width: 100%;
438
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
439
- border: 1px solid var(--c15t-border, #e3e3e3);
456
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
440
457
  border-radius: var(--c15t-radius-md, .5rem);
441
- background-color: var(--c15t-surface, #fff);
458
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
459
+ width: 100%;
460
+ min-height: 2rem;
442
461
  color: var(--c15t-text, #171717);
443
- font-family: inherit;
462
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
444
463
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
464
+ line-height: var(--c15t-line-height-tight, 1.25);
445
465
  cursor: pointer;
466
+ transition: border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), box-shadow var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
467
+ padding: .375rem .625rem;
446
468
  }
447
469
 
448
470
  .select-byJ1WM:focus {
449
- border-color: var(--c15t-primary, #335cff);
471
+ border-color: var(--c15t-devtools-focus-ring, #335cff);
472
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--c15t-devtools-focus-ring, #335cff) 25%, transparent);
450
473
  outline: none;
451
474
  }
452
475
 
453
476
  .grid-LlrmEz {
454
477
  gap: var(--c15t-space-sm, .5rem);
455
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
478
+ padding: var(--c15t-space-md, 1rem);
456
479
  display: grid;
457
480
  }
458
481
 
@@ -465,42 +488,51 @@ var __webpack_modules__ = {
465
488
  }
466
489
 
467
490
  .gridCard-Qm5xxI {
468
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, .75rem);
469
- border: 1px solid var(--c15t-border, #e3e3e3);
470
- border-radius: var(--c15t-radius-md, .5rem);
471
- background-color: var(--c15t-surface, #fff);
472
- transition: border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
473
491
  justify-content: space-between;
474
492
  align-items: center;
493
+ gap: var(--c15t-space-sm, .5rem);
494
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
495
+ border-radius: var(--c15t-radius-md, .5rem);
496
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
497
+ min-height: 2.75rem;
498
+ box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000000d);
499
+ transition: border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
500
+ padding: .5625rem .75rem;
475
501
  display: flex;
476
502
  }
477
503
 
478
504
  .gridCard-Qm5xxI:hover {
479
505
  border-color: var(--c15t-border-hover, #c9c9c9);
506
+ background-color: var(--c15t-devtools-surface-subtle, #fafafa);
480
507
  }
481
508
 
482
509
  .gridCardTitle-HjXETp {
483
510
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
484
511
  font-weight: var(--c15t-font-weight-medium, 500);
485
512
  color: var(--c15t-text, #171717);
513
+ line-height: var(--c15t-line-height-tight, 1.25);
486
514
  }
487
515
 
488
516
  .listItem-XUKGIo {
489
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-md, 1rem);
490
- border-bottom: 1px solid var(--c15t-border, #e3e3e3);
491
517
  justify-content: space-between;
492
518
  align-items: center;
519
+ gap: var(--c15t-space-sm, .5rem);
520
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
521
+ border-radius: var(--c15t-radius-md, .5rem);
522
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
523
+ margin-bottom: .375rem;
524
+ padding: .625rem .75rem;
493
525
  display: flex;
494
526
  }
495
527
 
496
528
  .listItem-XUKGIo:last-child {
497
- border-bottom: none;
529
+ margin-bottom: 0;
498
530
  }
499
531
 
500
532
  .listItemContent-WDBF1N {
501
533
  flex-direction: column;
502
534
  flex: 1;
503
- gap: 2px;
535
+ gap: .1875rem;
504
536
  min-width: 0;
505
537
  display: flex;
506
538
  }
@@ -514,6 +546,7 @@ var __webpack_modules__ = {
514
546
  .listItemDescription-E6JHyZ {
515
547
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
516
548
  color: var(--c15t-text-muted, #737373);
549
+ line-height: var(--c15t-line-height-tight, 1.25);
517
550
  text-overflow: ellipsis;
518
551
  white-space: nowrap;
519
552
  overflow: hidden;
@@ -527,8 +560,8 @@ var __webpack_modules__ = {
527
560
  }
528
561
 
529
562
  .section-a197cB {
530
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
531
- border-bottom: 1px solid var(--c15t-border, #e3e3e3);
563
+ border-bottom: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
564
+ padding: .75rem 1rem;
532
565
  }
533
566
 
534
567
  .section-a197cB:last-child {
@@ -536,9 +569,10 @@ var __webpack_modules__ = {
536
569
  }
537
570
 
538
571
  .sectionHeader-Xcljcw {
539
- margin-bottom: var(--c15t-space-sm, .5rem);
540
572
  justify-content: space-between;
541
573
  align-items: center;
574
+ gap: .5rem;
575
+ margin-bottom: .625rem;
542
576
  display: flex;
543
577
  }
544
578
 
@@ -547,13 +581,60 @@ var __webpack_modules__ = {
547
581
  font-weight: var(--c15t-font-weight-semibold, 600);
548
582
  color: var(--c15t-text-muted, #737373);
549
583
  text-transform: uppercase;
550
- letter-spacing: .5px;
584
+ letter-spacing: .04em;
585
+ }
586
+
587
+ .overrideField-keNdpJ {
588
+ flex-direction: column;
589
+ gap: .3125rem;
590
+ margin-bottom: 0;
591
+ display: flex;
592
+ }
593
+
594
+ .overrideLabel-ApMoTw {
595
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
596
+ font-weight: var(--c15t-font-weight-semibold, 600);
597
+ color: var(--c15t-text-muted, #737373);
598
+ line-height: var(--c15t-line-height-tight, 1.25);
599
+ }
600
+
601
+ .overrideHint-yCfwGt {
602
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
603
+ color: var(--c15t-text-muted, #737373);
604
+ line-height: var(--c15t-line-height-tight, 1.25);
605
+ margin-top: .5rem;
606
+ }
607
+
608
+ .overrideActions-imdcn7 {
609
+ border-top: 1px dashed var(--c15t-devtools-border-strong, #e3e3e3);
610
+ justify-content: space-between;
611
+ align-items: center;
612
+ gap: .5rem;
613
+ margin-top: .625rem;
614
+ padding-top: .625rem;
615
+ display: flex;
616
+ }
617
+
618
+ .overrideActionButtons-gYOx1e {
619
+ flex-wrap: wrap;
620
+ gap: .375rem;
621
+ display: flex;
622
+ }
623
+
624
+ .overrideStatus-sty_qS {
625
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
626
+ color: var(--c15t-text-muted, #737373);
627
+ }
628
+
629
+ .overrideStatusDirty-OUdDMw {
630
+ color: var(--c15t-devtools-badge-warning, #f59f0a);
551
631
  }
552
632
 
553
633
  .infoRow-RlB_0h {
554
- padding: var(--c15t-space-xs, .25rem) 0;
555
634
  justify-content: space-between;
556
635
  align-items: center;
636
+ gap: .5rem;
637
+ padding: .25rem 0;
557
638
  display: flex;
558
639
  }
559
640
 
@@ -566,6 +647,7 @@ var __webpack_modules__ = {
566
647
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
567
648
  font-weight: var(--c15t-font-weight-medium, 500);
568
649
  color: var(--c15t-text, #171717);
650
+ font-variant-numeric: tabular-nums;
569
651
  font-family: ui-monospace, Cascadia Code, Source Code Pro, Menlo, Consolas, DejaVu Sans Mono, monospace;
570
652
  }
571
653
 
@@ -576,18 +658,27 @@ var __webpack_modules__ = {
576
658
  flex-direction: column;
577
659
  justify-content: center;
578
660
  align-items: center;
661
+ gap: .375rem;
579
662
  display: flex;
580
663
  }
581
664
 
582
665
  .emptyStateIcon-WHFkX8 {
666
+ opacity: .55;
583
667
  width: 32px;
584
668
  height: 32px;
585
- margin-bottom: var(--c15t-space-sm, .5rem);
586
- opacity: .5;
587
669
  }
588
670
 
589
671
  .emptyStateText-TaLvAJ {
590
672
  font-size: var(--c15t-font-size-sm, .875rem);
673
+ line-height: var(--c15t-line-height-normal, 1.5);
674
+ }
675
+
676
+ .disconnectedState-dOtZBG {
677
+ padding: var(--c15t-space-xl, 2rem);
678
+ text-align: center;
679
+ font-size: var(--c15t-font-size-sm, .875rem);
680
+ color: var(--c15t-text-muted, #737373);
681
+ line-height: var(--c15t-line-height-normal, 1.5);
591
682
  }
592
683
 
593
684
  @media (prefers-reduced-motion: reduce) {
@@ -642,12 +733,20 @@ var __webpack_modules__ = {
642
733
  section: "section-a197cB",
643
734
  sectionHeader: "sectionHeader-Xcljcw",
644
735
  sectionTitle: "sectionTitle-RUiFld",
736
+ overrideField: "overrideField-keNdpJ",
737
+ overrideLabel: "overrideLabel-ApMoTw",
738
+ overrideHint: "overrideHint-yCfwGt",
739
+ overrideActions: "overrideActions-imdcn7",
740
+ overrideActionButtons: "overrideActionButtons-gYOx1e",
741
+ overrideStatus: "overrideStatus-sty_qS",
742
+ overrideStatusDirty: "overrideStatusDirty-OUdDMw",
645
743
  infoRow: "infoRow-RlB_0h",
646
744
  infoLabel: "infoLabel-_pbK33",
647
745
  infoValue: "infoValue-flMl_e",
648
746
  emptyState: "emptyState-QcmzTQ",
649
747
  emptyStateIcon: "emptyStateIcon-WHFkX8",
650
- emptyStateText: "emptyStateText-TaLvAJ"
748
+ emptyStateText: "emptyStateText-TaLvAJ",
749
+ disconnectedState: "disconnectedState-dOtZBG"
651
750
  };
652
751
  const __rspack_default_export = ___CSS_LOADER_EXPORT___;
653
752
  },
@@ -676,9 +775,9 @@ var __webpack_modules__ = {
676
775
  .floatingButton-Gw8MtJ {
677
776
  width: var(--c15t-devtools-button-size, 40px);
678
777
  height: var(--c15t-devtools-button-size, 40px);
679
- border: 1px solid var(--c15t-border, #e3e3e3);
778
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
680
779
  border-radius: var(--c15t-radius-full, 9999px);
681
- background-color: var(--c15t-surface, #fff);
780
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
682
781
  box-shadow: var(--c15t-shadow-lg, 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a);
683
782
  cursor: grab;
684
783
  z-index: var(--c15t-devtools-z-index, 99999);
@@ -706,13 +805,13 @@ var __webpack_modules__ = {
706
805
  }
707
806
 
708
807
  .floatingButton-Gw8MtJ:focus-visible {
709
- outline: 2px solid var(--c15t-primary, #335cff);
710
- outline-offset: 2px;
808
+ box-shadow: var(--c15t-shadow-lg, 0 8px 24px #0000001f), 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
809
+ outline: none;
711
810
  }
712
811
 
713
812
  .floatingButton-Gw8MtJ:active {
714
813
  cursor: grabbing;
715
- transform: scale(1.02);
814
+ transform: scale(.98);
716
815
  }
717
816
 
718
817
  .floatingButtonIcon-cHWefk {
@@ -753,6 +852,7 @@ var __webpack_modules__ = {
753
852
 
754
853
  .backdrop-LhVMB5 {
755
854
  background-color: var(--c15t-overlay, #00000080);
855
+ backdrop-filter: blur(1px);
756
856
  z-index: calc(var(--c15t-devtools-z-index, 99999) + 1);
757
857
  position: fixed;
758
858
  inset: 0;
@@ -761,10 +861,10 @@ var __webpack_modules__ = {
761
861
  .panel-jtWove {
762
862
  width: var(--c15t-devtools-panel-width, 480px);
763
863
  max-height: var(--c15t-devtools-panel-max-height, 560px);
764
- background-color: var(--c15t-surface, #fff);
765
- border: 1px solid var(--c15t-border, #e3e3e3);
864
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
865
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
766
866
  border-radius: var(--c15t-radius-lg, .75rem);
767
- box-shadow: var(--c15t-shadow-lg, 0 8px 24px #0000001f);
867
+ box-shadow: var(--c15t-shadow-lg, 0 10px 28px #00000029);
768
868
  z-index: calc(var(--c15t-devtools-z-index, 99999) + 2);
769
869
  flex-direction: column;
770
870
  display: flex;
@@ -797,11 +897,11 @@ var __webpack_modules__ = {
797
897
  }
798
898
 
799
899
  .header-xluoTr {
800
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
801
- border-bottom: 1px solid var(--c15t-border, #e3e3e3);
900
+ border-bottom: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
802
901
  background-color: var(--c15t-devtools-surface-muted, #f5f5f5);
803
902
  justify-content: space-between;
804
903
  align-items: center;
904
+ padding: .6875rem .875rem;
805
905
  display: flex;
806
906
  }
807
907
 
@@ -810,6 +910,7 @@ var __webpack_modules__ = {
810
910
  gap: var(--c15t-space-sm, .5rem);
811
911
  font-size: var(--c15t-font-size-sm, .875rem);
812
912
  font-weight: var(--c15t-font-weight-semibold, 600);
913
+ line-height: var(--c15t-line-height-tight, 1.25);
813
914
  color: var(--c15t-text, #171717);
814
915
  display: flex;
815
916
  }
@@ -821,9 +922,9 @@ var __webpack_modules__ = {
821
922
  }
822
923
 
823
924
  .closeButton-Yto0Nb {
824
- border-radius: var(--c15t-radius-sm, .25rem);
825
- width: 28px;
826
- height: 28px;
925
+ border-radius: var(--c15t-radius-md, .5rem);
926
+ width: 2rem;
927
+ height: 2rem;
827
928
  color: var(--c15t-text-muted, #737373);
828
929
  cursor: pointer;
829
930
  transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
@@ -836,13 +937,13 @@ var __webpack_modules__ = {
836
937
  }
837
938
 
838
939
  .closeButton-Yto0Nb:hover {
839
- background-color: var(--c15t-surface-hover, #f7f7f7);
940
+ background-color: var(--c15t-devtools-surface-subtle, #f7f7f7);
840
941
  color: var(--c15t-text, #171717);
841
942
  }
842
943
 
843
944
  .closeButton-Yto0Nb:focus-visible {
844
- outline: 2px solid var(--c15t-primary, #335cff);
845
- outline-offset: 1px;
945
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
946
+ outline: none;
846
947
  }
847
948
 
848
949
  .closeButtonIcon-fVlR1I {
@@ -850,33 +951,71 @@ var __webpack_modules__ = {
850
951
  height: 16px;
851
952
  }
852
953
 
954
+ .inlineActionButton-Ky8BmN {
955
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
956
+ border-radius: var(--c15t-radius-sm, .375rem);
957
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
958
+ min-height: 1.625rem;
959
+ color: var(--c15t-text, #171717);
960
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
961
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
962
+ font-weight: var(--c15t-font-weight-medium, 500);
963
+ line-height: var(--c15t-line-height-tight, 1.25);
964
+ cursor: pointer;
965
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
966
+ justify-content: center;
967
+ align-items: center;
968
+ padding: .25rem .5rem;
969
+ display: inline-flex;
970
+ }
971
+
972
+ .inlineActionButton-Ky8BmN:hover {
973
+ background-color: var(--c15t-devtools-surface-subtle, #f7f7f7);
974
+ border-color: var(--c15t-border-hover, #c9c9c9);
975
+ }
976
+
977
+ .inlineActionButton-Ky8BmN:focus-visible {
978
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
979
+ outline: none;
980
+ }
981
+
853
982
  .content-yDMYfG {
983
+ scrollbar-gutter: stable;
854
984
  overscroll-behavior: contain;
855
985
  -webkit-overflow-scrolling: touch;
986
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
856
987
  flex: auto;
857
988
  min-height: 200px;
858
- overflow: hidden auto;
989
+ overflow: hidden scroll;
859
990
  }
860
991
 
861
992
  .footer-ESbmwQ {
862
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-md, 1rem);
863
- border-top: 1px solid var(--c15t-border, #e3e3e3);
993
+ border-top: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
864
994
  background-color: var(--c15t-devtools-surface-muted, #f5f5f5);
865
995
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
866
996
  color: var(--c15t-text-muted, #737373);
867
997
  justify-content: space-between;
868
998
  align-items: center;
999
+ gap: .5rem;
1000
+ padding: .5rem .75rem;
869
1001
  display: flex;
870
1002
  }
871
1003
 
872
1004
  .footerStatus-rlb99A {
873
1005
  align-items: center;
874
- gap: var(--c15t-space-xs, .25rem);
1006
+ gap: .375rem;
1007
+ min-width: 0;
875
1008
  display: flex;
876
1009
  }
877
1010
 
1011
+ .footerMeta-Vdtxdk {
1012
+ opacity: .75;
1013
+ white-space: nowrap;
1014
+ }
1015
+
878
1016
  .statusDot-hYJoej {
879
1017
  border-radius: var(--c15t-radius-full, 9999px);
1018
+ flex-shrink: 0;
880
1019
  width: 6px;
881
1020
  height: 6px;
882
1021
  }
@@ -915,6 +1054,7 @@ var __webpack_modules__ = {
915
1054
  font-size: var(--c15t-font-size-sm, .875rem);
916
1055
  color: var(--c15t-text-muted, #737373);
917
1056
  max-width: 280px;
1057
+ line-height: var(--c15t-line-height-normal, 1.5);
918
1058
  }
919
1059
 
920
1060
  @media (prefers-reduced-motion: reduce) {
@@ -931,17 +1071,17 @@ var __webpack_modules__ = {
931
1071
  }
932
1072
 
933
1073
  .dropdownMenu-aKK18l {
934
- min-width: 200px;
935
- padding: var(--c15t-space-xs, .25rem);
936
- border: 1px solid var(--c15t-border, #e3e3e3);
1074
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
937
1075
  border-radius: var(--c15t-radius-lg, .75rem);
938
- background-color: var(--c15t-surface, #fff);
1076
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
1077
+ min-width: 200px;
939
1078
  box-shadow: var(--c15t-shadow-lg, 0 8px 24px #0000001f);
940
1079
  z-index: calc(var(--c15t-devtools-z-index, 99999) + 1);
941
1080
  opacity: 0;
942
1081
  transform-origin: 0 100%;
943
1082
  pointer-events: none;
944
1083
  transition: opacity var(--c15t-duration-fast, .1s) var(--c15t-easing-out, cubic-bezier(.215, .61, .355, 1)), transform var(--c15t-duration-fast, .1s) var(--c15t-easing-out, cubic-bezier(.215, .61, .355, 1));
1084
+ padding: .375rem;
945
1085
  position: fixed;
946
1086
  transform: scale(.95)translateY(8px);
947
1087
  }
@@ -969,34 +1109,35 @@ var __webpack_modules__ = {
969
1109
  }
970
1110
 
971
1111
  .menuItem-kBbHRP {
972
- align-items: center;
973
- gap: var(--c15t-space-sm, .5rem);
974
- width: 100%;
975
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, .75rem);
976
1112
  border-radius: var(--c15t-radius-md, .5rem);
1113
+ width: 100%;
977
1114
  color: var(--c15t-text, #171717);
978
- font-size: var(--c15t-font-size-sm, .875rem);
1115
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
979
1116
  font-weight: var(--c15t-font-weight-medium, 500);
1117
+ line-height: var(--c15t-line-height-tight, 1.25);
980
1118
  text-align: left;
981
1119
  cursor: pointer;
982
1120
  transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
983
1121
  background: none;
984
1122
  border: none;
1123
+ align-items: center;
1124
+ gap: .625rem;
1125
+ padding: .5rem .625rem;
985
1126
  display: flex;
986
1127
  }
987
1128
 
988
1129
  .menuItem-kBbHRP:hover {
989
- background-color: var(--c15t-surface-hover, #f2f2f2);
1130
+ background-color: var(--c15t-devtools-surface-subtle, #f2f2f2);
990
1131
  }
991
1132
 
992
1133
  .menuItem-kBbHRP:focus-visible {
993
- outline: 2px solid var(--c15t-primary, #335cff);
994
- outline-offset: -2px;
1134
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
1135
+ outline: none;
995
1136
  }
996
1137
 
997
1138
  .menuItemIcon-P3pP5K {
998
- width: 20px;
999
- height: 20px;
1139
+ width: 1rem;
1140
+ height: 1rem;
1000
1141
  color: var(--c15t-text-muted, #737373);
1001
1142
  flex-shrink: 0;
1002
1143
  }
@@ -1009,6 +1150,7 @@ var __webpack_modules__ = {
1009
1150
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
1010
1151
  color: var(--c15t-text-muted, #737373);
1011
1152
  font-weight: var(--c15t-font-weight-normal, 400);
1153
+ margin-top: .125rem;
1012
1154
  }
1013
1155
 
1014
1156
  .menuItemContent-hBlruV {
@@ -1024,8 +1166,8 @@ var __webpack_modules__ = {
1024
1166
  .menuItemToggleTrack-gDp_f3 {
1025
1167
  background-color: var(--c15t-switch-track, #d9d9d9);
1026
1168
  border-radius: var(--c15t-radius-full, 9999px);
1027
- width: 36px;
1028
- height: 20px;
1169
+ width: 2rem;
1170
+ height: 1.25rem;
1029
1171
  transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1030
1172
  position: relative;
1031
1173
  }
@@ -1033,21 +1175,22 @@ var __webpack_modules__ = {
1033
1175
  .menuItemToggleThumb-ioqqyc {
1034
1176
  background-color: var(--c15t-switch-thumb, #fff);
1035
1177
  border-radius: var(--c15t-radius-full, 9999px);
1036
- width: 16px;
1037
- height: 16px;
1038
- box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000001a);
1178
+ width: .75rem;
1179
+ height: .75rem;
1180
+ box-shadow: 0 0 0 1px var(--c15t-border, #e3e3e3);
1039
1181
  transition: transform var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1040
1182
  position: absolute;
1041
- top: 2px;
1183
+ top: 50%;
1042
1184
  left: 2px;
1185
+ transform: translateY(-50%);
1043
1186
  }
1044
1187
 
1045
1188
  .menuItemToggleChecked-K3BPtk .menuItemToggleTrack-gDp_f3 {
1046
- background-color: var(--c15t-switch-track-checked, #335cff);
1189
+ background-color: var(--c15t-switch-track-active, #335cff);
1047
1190
  }
1048
1191
 
1049
1192
  .menuItemToggleChecked-K3BPtk .menuItemToggleThumb-ioqqyc {
1050
- transform: translateX(16px);
1193
+ transform: translate(1rem, -50%);
1051
1194
  }
1052
1195
 
1053
1196
  .menuDivider-JIBdhU {
@@ -1083,9 +1226,11 @@ var __webpack_modules__ = {
1083
1226
  headerLogo: "headerLogo-PxJ_w1",
1084
1227
  closeButton: "closeButton-Yto0Nb",
1085
1228
  closeButtonIcon: "closeButtonIcon-fVlR1I",
1229
+ inlineActionButton: "inlineActionButton-Ky8BmN",
1086
1230
  content: "content-yDMYfG",
1087
1231
  footer: "footer-ESbmwQ",
1088
1232
  footerStatus: "footerStatus-rlb99A",
1233
+ footerMeta: "footerMeta-Vdtxdk",
1089
1234
  statusDot: "statusDot-hYJoej",
1090
1235
  statusConnected: "statusConnected-hPSUgS",
1091
1236
  statusDisconnected: "statusDisconnected-HIpcee",
@@ -1124,55 +1269,63 @@ var __webpack_modules__ = {
1124
1269
  ___CSS_LOADER_EXPORT___.push([
1125
1270
  module.id,
1126
1271
  `.tabList-IyuiBE {
1127
- gap: var(--c15t-space-xs, .25rem);
1128
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
1129
- border-bottom: 1px solid var(--c15t-border, #e3e3e3);
1130
- background-color: var(--c15t-surface, #fff);
1131
- scrollbar-width: none;
1132
- -ms-overflow-style: none;
1272
+ border-bottom: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
1273
+ background-color: var(--c15t-devtools-surface-subtle, #fafafa);
1274
+ align-items: center;
1275
+ gap: .375rem;
1276
+ padding: .75rem;
1133
1277
  display: flex;
1134
- overflow-x: auto;
1135
1278
  }
1136
1279
 
1137
- .tabList-IyuiBE::-webkit-scrollbar {
1138
- display: none;
1280
+ .tabStrip-_KrWe4 {
1281
+ align-items: center;
1282
+ gap: var(--c15t-space-xs, .25rem);
1283
+ flex: auto;
1284
+ min-width: 0;
1285
+ display: flex;
1286
+ overflow: hidden;
1139
1287
  }
1140
1288
 
1141
1289
  .tab-yfDEqg {
1142
- align-items: center;
1143
- gap: var(--c15t-space-xs, .25rem);
1144
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
1145
1290
  border-radius: var(--c15t-radius-md, .5rem);
1291
+ min-height: 1.875rem;
1146
1292
  color: var(--c15t-text-muted, #737373);
1147
- font-family: inherit;
1293
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
1148
1294
  font-size: var(--c15t-devtools-font-size-xs, .75rem);
1149
1295
  font-weight: var(--c15t-font-weight-medium, 500);
1296
+ line-height: var(--c15t-line-height-tight, 1.25);
1150
1297
  cursor: pointer;
1151
1298
  white-space: nowrap;
1152
- transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1299
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), box-shadow var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1153
1300
  background-color: #0000;
1154
- border: none;
1301
+ border: 1px solid #0000;
1302
+ flex-shrink: 0;
1303
+ align-items: center;
1304
+ gap: .375rem;
1305
+ padding: .25rem .625rem;
1155
1306
  display: flex;
1156
1307
  }
1157
1308
 
1158
1309
  .tab-yfDEqg:hover {
1159
- background-color: var(--c15t-surface-hover, #f7f7f7);
1310
+ background-color: var(--c15t-devtools-surface-muted, #f7f7f7);
1160
1311
  color: var(--c15t-text, #171717);
1161
1312
  }
1162
1313
 
1163
1314
  .tab-yfDEqg:focus-visible {
1164
- outline: 2px solid var(--c15t-primary, #335cff);
1165
- outline-offset: 1px;
1315
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
1316
+ outline: none;
1166
1317
  }
1167
1318
 
1168
1319
  .tabActive-r4hing {
1169
- background-color: var(--c15t-primary, #335cff);
1170
- color: var(--c15t-text-on-primary, #fff);
1320
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
1321
+ border-color: var(--c15t-devtools-border-strong, #e3e3e3);
1322
+ color: var(--c15t-text, #171717);
1323
+ box-shadow: var(--c15t-shadow-sm, 0 1px 2px #0000000d);
1171
1324
  }
1172
1325
 
1173
1326
  .tabActive-r4hing:hover {
1174
- background-color: var(--c15t-primary-hover, #03f);
1175
- color: var(--c15t-text-on-primary, #fff);
1327
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
1328
+ color: var(--c15t-text, #171717);
1176
1329
  }
1177
1330
 
1178
1331
  .tabIcon-U9tnu0 {
@@ -1181,6 +1334,131 @@ var __webpack_modules__ = {
1181
1334
  height: 14px;
1182
1335
  }
1183
1336
 
1337
+ .tabHidden-HBXYSd {
1338
+ display: none;
1339
+ }
1340
+
1341
+ .overflowContainer-TTw9DO {
1342
+ flex-shrink: 0;
1343
+ position: relative;
1344
+ }
1345
+
1346
+ .overflowContainerHidden-sQa_XZ {
1347
+ display: none;
1348
+ }
1349
+
1350
+ .overflowButton-tKq4FF {
1351
+ border-radius: var(--c15t-radius-md, .5rem);
1352
+ width: 1.875rem;
1353
+ height: 1.875rem;
1354
+ color: var(--c15t-text-muted, #737373);
1355
+ cursor: pointer;
1356
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1357
+ background-color: #0000;
1358
+ border: 1px solid #0000;
1359
+ justify-content: center;
1360
+ align-items: center;
1361
+ padding: 0;
1362
+ display: inline-flex;
1363
+ }
1364
+
1365
+ .overflowButton-tKq4FF:hover {
1366
+ background-color: var(--c15t-devtools-surface-muted, #f7f7f7);
1367
+ color: var(--c15t-text, #171717);
1368
+ }
1369
+
1370
+ .overflowButton-tKq4FF:focus-visible {
1371
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
1372
+ outline: none;
1373
+ }
1374
+
1375
+ .overflowButtonIcon-FSurfC {
1376
+ justify-content: center;
1377
+ align-items: center;
1378
+ width: 14px;
1379
+ height: 14px;
1380
+ display: inline-flex;
1381
+ }
1382
+
1383
+ .overflowMenu-TST0eZ {
1384
+ border: 1px solid var(--c15t-devtools-border-strong, #e3e3e3);
1385
+ border-radius: var(--c15t-radius-md, .5rem);
1386
+ background-color: var(--c15t-devtools-surface-elevated, #fff);
1387
+ min-width: 10rem;
1388
+ box-shadow: var(--c15t-shadow-md, 0 4px 12px #00000014);
1389
+ opacity: 0;
1390
+ transform-origin: 100% 0;
1391
+ pointer-events: none;
1392
+ z-index: 10;
1393
+ transition: opacity var(--c15t-duration-fast, .1s) var(--c15t-easing-out, cubic-bezier(.215, .61, .355, 1)), transform var(--c15t-duration-fast, .1s) var(--c15t-easing-out, cubic-bezier(.215, .61, .355, 1));
1394
+ flex-direction: column;
1395
+ gap: .125rem;
1396
+ padding: .3125rem;
1397
+ display: flex;
1398
+ position: absolute;
1399
+ top: calc(100% + .375rem);
1400
+ right: 0;
1401
+ transform: translateY(-4px)scale(.98);
1402
+ }
1403
+
1404
+ .overflowMenu-TST0eZ[data-state="open"] {
1405
+ opacity: 1;
1406
+ pointer-events: auto;
1407
+ transform: translateY(0)scale(1);
1408
+ }
1409
+
1410
+ .overflowItem-y5Pz7k {
1411
+ border-radius: var(--c15t-radius-sm, .375rem);
1412
+ min-height: 1.75rem;
1413
+ color: var(--c15t-text, #171717);
1414
+ font-family: var(--c15t-font-family, system-ui, -apple-system, sans-serif);
1415
+ font-size: var(--c15t-devtools-font-size-xs, .75rem);
1416
+ font-weight: var(--c15t-font-weight-medium, 500);
1417
+ line-height: var(--c15t-line-height-tight, 1.25);
1418
+ text-align: left;
1419
+ cursor: pointer;
1420
+ white-space: nowrap;
1421
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
1422
+ background-color: #0000;
1423
+ border: none;
1424
+ align-items: center;
1425
+ gap: .5rem;
1426
+ padding: .3125rem .5rem;
1427
+ display: inline-flex;
1428
+ }
1429
+
1430
+ .overflowItem-y5Pz7k:hover {
1431
+ background-color: var(--c15t-devtools-surface-subtle, #fafafa);
1432
+ }
1433
+
1434
+ .overflowItem-y5Pz7k:focus-visible {
1435
+ box-shadow: 0 0 0 2px var(--c15t-devtools-focus-ring, #335cff);
1436
+ outline: none;
1437
+ }
1438
+
1439
+ .overflowItemActive-mzVG1T {
1440
+ background-color: var(--c15t-devtools-accent-soft, #ebefff);
1441
+ color: var(--c15t-text, #171717);
1442
+ }
1443
+
1444
+ .overflowItemDisabled-dcHX3K {
1445
+ opacity: .5;
1446
+ cursor: not-allowed;
1447
+ }
1448
+
1449
+ .overflowItemIcon-fz291V {
1450
+ flex-shrink: 0;
1451
+ justify-content: center;
1452
+ align-items: center;
1453
+ width: 14px;
1454
+ height: 14px;
1455
+ display: inline-flex;
1456
+ }
1457
+
1458
+ .overflowItemHidden-k4aawi {
1459
+ display: none;
1460
+ }
1461
+
1184
1462
  .tabDisabled-lDuv5l {
1185
1463
  opacity: .5;
1186
1464
  cursor: not-allowed;
@@ -1201,7 +1479,7 @@ var __webpack_modules__ = {
1201
1479
  }
1202
1480
 
1203
1481
  @media (prefers-reduced-motion: reduce) {
1204
- .tab-yfDEqg {
1482
+ .tab-yfDEqg, .overflowButton-tKq4FF, .overflowMenu-TST0eZ, .overflowItem-y5Pz7k {
1205
1483
  transition: none;
1206
1484
  }
1207
1485
  }
@@ -1217,9 +1495,21 @@ var __webpack_modules__ = {
1217
1495
  ]);
1218
1496
  ___CSS_LOADER_EXPORT___.locals = {
1219
1497
  tabList: "tabList-IyuiBE",
1498
+ tabStrip: "tabStrip-_KrWe4",
1220
1499
  tab: "tab-yfDEqg",
1221
1500
  tabActive: "tabActive-r4hing",
1222
1501
  tabIcon: "tabIcon-U9tnu0",
1502
+ tabHidden: "tabHidden-HBXYSd",
1503
+ overflowContainer: "overflowContainer-TTw9DO",
1504
+ overflowContainerHidden: "overflowContainerHidden-sQa_XZ",
1505
+ overflowButton: "overflowButton-tKq4FF",
1506
+ overflowButtonIcon: "overflowButtonIcon-FSurfC",
1507
+ overflowMenu: "overflowMenu-TST0eZ",
1508
+ overflowItem: "overflowItem-y5Pz7k",
1509
+ overflowItemActive: "overflowItemActive-mzVG1T",
1510
+ overflowItemDisabled: "overflowItemDisabled-dcHX3K",
1511
+ overflowItemIcon: "overflowItemIcon-fz291V",
1512
+ overflowItemHidden: "overflowItemHidden-k4aawi",
1223
1513
  tabDisabled: "tabDisabled-lDuv5l",
1224
1514
  tabPanel: "tabPanel-QKO8FX",
1225
1515
  tabPanelActive: "tabPanelActive-mrNlGE"
@@ -1243,6 +1533,15 @@ var __webpack_modules__ = {
1243
1533
  --c15t-devtools-button-size: 40px;
1244
1534
  --c15t-devtools-z-index: 99999;
1245
1535
  --c15t-devtools-surface-muted: var(--c15t-surface-hover, #f7f7f7);
1536
+ --c15t-devtools-surface-subtle: #fafafa;
1537
+ --c15t-devtools-surface-elevated: var(--c15t-surface, #fff);
1538
+ --c15t-devtools-border-strong: var(--c15t-border, #e3e3e3);
1539
+ --c15t-devtools-code-surface: #f7f7f7;
1540
+ --c15t-devtools-accent-soft: #ebefff;
1541
+ --c15t-devtools-focus-ring: var(--c15t-primary, #335cff);
1542
+ --c15t-devtools-text-muted: var(--c15t-text-muted, #737373);
1543
+ --c15t-devtools-font-size-sm: var(--c15t-font-size-sm, .875rem);
1544
+ --c15t-surface-muted: var(--c15t-devtools-surface-muted, #f7f7f7);
1246
1545
  --c15t-devtools-font-size-xs: .75rem;
1247
1546
  --c15t-devtools-badge-success: #21c45d;
1248
1547
  --c15t-devtools-badge-success-bg: #e4fbed;
@@ -1256,8 +1555,37 @@ var __webpack_modules__ = {
1256
1555
  --c15t-devtools-badge-neutral-bg: #f0f0f0;
1257
1556
  }
1258
1557
 
1558
+ :is(:global(.c15t-light), :global(.light)) {
1559
+ --c15t-devtools-surface-muted: var(--c15t-surface-hover, #f7f7f7);
1560
+ --c15t-devtools-surface-subtle: #fafafa;
1561
+ --c15t-devtools-surface-elevated: var(--c15t-surface, #fff);
1562
+ --c15t-devtools-border-strong: var(--c15t-border, #e3e3e3);
1563
+ --c15t-devtools-code-surface: #f7f7f7;
1564
+ --c15t-devtools-accent-soft: #ebefff;
1565
+ --c15t-devtools-badge-success-bg: #e4fbed;
1566
+ --c15t-devtools-badge-error-bg: #fde7e7;
1567
+ --c15t-devtools-badge-warning-bg: #fef7dc;
1568
+ --c15t-devtools-badge-info-bg: #dcebfe;
1569
+ --c15t-devtools-badge-neutral-bg: #f0f0f0;
1570
+ }
1571
+
1572
+ @supports (color: color-mix(in srgb, white, black)) {
1573
+ :root {
1574
+ --c15t-devtools-surface-muted: color-mix(in srgb, var(--c15t-surface-hover, #f7f7f7) 85%, var(--c15t-surface, #fff));
1575
+ --c15t-devtools-surface-subtle: color-mix(in srgb, var(--c15t-surface-hover, #f7f7f7) 55%, var(--c15t-surface, #fff));
1576
+ --c15t-devtools-border-strong: color-mix(in srgb, var(--c15t-border, #e3e3e3) 80%, var(--c15t-text-muted, #737373));
1577
+ --c15t-devtools-code-surface: color-mix(in srgb, var(--c15t-surface-hover, #f7f7f7) 70%, var(--c15t-surface, #fff));
1578
+ --c15t-devtools-accent-soft: color-mix(in srgb, var(--c15t-primary, #335cff) 12%, transparent);
1579
+ }
1580
+ }
1581
+
1259
1582
  :is(:global(.c15t-dark), :global(.dark)) {
1260
1583
  --c15t-devtools-surface-muted: var(--c15t-surface-hover, #292929);
1584
+ --c15t-devtools-surface-subtle: #242424;
1585
+ --c15t-devtools-surface-elevated: var(--c15t-surface, #1a1a1a);
1586
+ --c15t-devtools-border-strong: var(--c15t-border, #3d3d3d);
1587
+ --c15t-devtools-code-surface: #212121;
1588
+ --c15t-devtools-accent-soft: #335cff33;
1261
1589
  --c15t-devtools-badge-success-bg: #21c45d33;
1262
1590
  --c15t-devtools-badge-error-bg: #ef434333;
1263
1591
  --c15t-devtools-badge-warning-bg: #f59f0a33;
@@ -1266,8 +1594,13 @@ var __webpack_modules__ = {
1266
1594
  }
1267
1595
 
1268
1596
  @media (prefers-color-scheme: dark) {
1269
- :root {
1597
+ :global(:root:not(.light):not(.c15t-light)) {
1270
1598
  --c15t-devtools-surface-muted: var(--c15t-surface-hover, #292929);
1599
+ --c15t-devtools-surface-subtle: #242424;
1600
+ --c15t-devtools-surface-elevated: var(--c15t-surface, #1a1a1a);
1601
+ --c15t-devtools-border-strong: var(--c15t-border, #3d3d3d);
1602
+ --c15t-devtools-code-surface: #212121;
1603
+ --c15t-devtools-accent-soft: #335cff33;
1271
1604
  --c15t-devtools-badge-success-bg: #21c45d33;
1272
1605
  --c15t-devtools-badge-error-bg: #ef434333;
1273
1606
  --c15t-devtools-badge-warning-bg: #f59f0a33;
@@ -1775,7 +2108,7 @@ function renderer_button(options = {}) {
1775
2108
  type: options.type ?? 'button'
1776
2109
  });
1777
2110
  }
1778
- function span(options = {}) {
2111
+ function renderer_span(options = {}) {
1779
2112
  return createElement({
1780
2113
  ...options,
1781
2114
  tag: 'span'
@@ -1854,7 +2187,8 @@ panel_module_options.domAPI = styleDomAPI_default();
1854
2187
  panel_module_options.insertStyleElement = insertStyleElement_default();
1855
2188
  injectStylesIntoStyleTag_default()(panel_module.A, panel_module_options);
1856
2189
  const styles_panel_module = panel_module.A && panel_module.A.locals ? panel_module.A.locals : void 0;
1857
- const PREFERENCE_TRIGGER_SELECTORS = '[data-c15t-trigger], [aria-label*="privacy settings"], [aria-label*="preference"]';
2190
+ const PREFERENCE_TRIGGER_SELECTORS = '[data-c15t-trigger], [aria-label*="privacy settings" i], [aria-label*="preference" i]';
2191
+ const PREVIOUS_DISPLAY_ATTR = 'data-c15t-devtools-prev-display';
1858
2192
  function detectPreferenceTrigger() {
1859
2193
  const triggers = document.querySelectorAll(PREFERENCE_TRIGGER_SELECTORS);
1860
2194
  return triggers.length > 0;
@@ -1864,11 +2198,23 @@ function getPreferenceTriggerElements() {
1864
2198
  }
1865
2199
  function setPreferenceTriggerVisibility(visible) {
1866
2200
  const elements = getPreferenceTriggerElements();
1867
- for (const el of elements)el.style.display = visible ? '' : 'none';
2201
+ for (const el of elements){
2202
+ if (visible) {
2203
+ const previousDisplay = el.getAttribute(PREVIOUS_DISPLAY_ATTR);
2204
+ if (null === previousDisplay) el.style.removeProperty('display');
2205
+ else {
2206
+ el.style.display = previousDisplay;
2207
+ el.removeAttribute(PREVIOUS_DISPLAY_ATTR);
2208
+ }
2209
+ continue;
2210
+ }
2211
+ if (!el.hasAttribute(PREVIOUS_DISPLAY_ATTR)) el.setAttribute(PREVIOUS_DISPLAY_ATTR, el.style.display);
2212
+ el.style.display = 'none';
2213
+ }
1868
2214
  }
1869
- function getPreferenceCenterOpener() {
2215
+ function getPreferenceCenterOpener(namespace = 'c15tStore') {
1870
2216
  const win = window;
1871
- const store = win.c15tStore;
2217
+ const store = win[namespace];
1872
2218
  if (store && 'function' == typeof store.getState) {
1873
2219
  const state = store.getState();
1874
2220
  if ('function' == typeof state.setActiveUI) return ()=>{
@@ -1877,6 +2223,7 @@ function getPreferenceCenterOpener() {
1877
2223
  }
1878
2224
  return null;
1879
2225
  }
2226
+ const version = '2.0.0-rc.4';
1880
2227
  const DEVTOOLS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 445" aria-label="c15t">
1881
2228
  <path fill="currentColor" d="M223.178.313c39.064 0 70.732 31.668 70.732 70.732-.001 39.064-31.668 70.731-70.732 70.731-12.181 0-23.642-3.079-33.649-8.502l-55.689 55.689a70.267 70.267 0 0 1 5.574 13.441h167.531c8.695-29.217 35.762-50.523 67.804-50.523 39.064 0 70.731 31.668 70.731 70.732s-31.668 70.732-70.731 70.732c-32.042 0-59.108-21.306-67.803-50.523H139.413a70.417 70.417 0 0 1-7.888 17.396l54.046 54.046c10.893-6.851 23.786-10.815 37.605-10.815 39.064 0 70.732 31.669 70.732 70.733 0 39.064-31.668 70.731-70.732 70.731s-70.732-31.667-70.732-70.731c0-10.518 2.296-20.499 6.414-29.471l-57.78-57.78c-8.972 4.117-18.952 6.414-29.47 6.414-39.063 0-70.731-31.668-70.732-70.732 0-39.064 31.669-70.732 70.733-70.732 12.18 0 23.642 3.079 33.649 8.502l55.688-55.688c-5.423-10.007-8.502-21.469-8.502-33.65 0-39.064 31.668-70.733 70.732-70.733Zm0 343.555c-16.742 0-30.314 13.572-30.314 30.314 0 16.741 13.572 30.313 30.314 30.313s30.314-13.572 30.314-30.313c0-16.742-13.572-30.314-30.314-30.314ZM71.611 192.299c-16.742 0-30.315 13.572-30.315 30.314s13.573 30.314 30.315 30.314c16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314Zm303.138 0c-16.729 0-30.294 13.551-30.315 30.275l.001.039-.001.038c.021 16.725 13.586 30.276 30.315 30.276 16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314ZM223.178 40.73c-16.742 0-30.314 13.573-30.314 30.315s13.573 30.313 30.314 30.313c16.742 0 30.313-13.572 30.314-30.313 0-16.742-13.572-30.314-30.314-30.315Z"/>
1882
2229
  </svg>`;
@@ -1957,7 +2304,7 @@ function createDropdownMenu(options) {
1957
2304
  const labelContainer = renderer_div({
1958
2305
  className: styles_panel_module.menuItemContent ?? ''
1959
2306
  });
1960
- const label = span({
2307
+ const label = renderer_span({
1961
2308
  className: styles_panel_module.menuItemLabel ?? '',
1962
2309
  text: item.label
1963
2310
  });
@@ -2079,8 +2426,13 @@ function getPositionClass(position) {
2079
2426
  return styles_panel_module.positionTopLeft ?? '';
2080
2427
  }
2081
2428
  }
2429
+ function formatRetryDelay(delayMs) {
2430
+ if (null === delayMs) return 'n/a';
2431
+ if (delayMs < 1000) return `${delayMs}ms`;
2432
+ return `${(delayMs / 1000).toFixed(1)}s`;
2433
+ }
2082
2434
  function createPanel(options) {
2083
- const { stateManager, storeConnector, onRenderContent, enableUnifiedMode = true } = options;
2435
+ const { stateManager, storeConnector, onRenderContent, namespace = 'c15tStore', enableUnifiedMode = true } = options;
2084
2436
  let removePortal = null;
2085
2437
  let isAnimatingOut = false;
2086
2438
  let draggable = null;
@@ -2109,7 +2461,7 @@ function createPanel(options) {
2109
2461
  return;
2110
2462
  }
2111
2463
  hasPreferenceTrigger = detectPreferenceTrigger();
2112
- const preferenceCenterOpener = getPreferenceCenterOpener();
2464
+ const preferenceCenterOpener = getPreferenceCenterOpener(namespace);
2113
2465
  useUnifiedMode = hasPreferenceTrigger && null !== preferenceCenterOpener;
2114
2466
  if (useUnifiedMode && !dropdownMenu) {
2115
2467
  dropdownMenu = createDropdownMenu({
@@ -2129,7 +2481,7 @@ function createPanel(options) {
2129
2481
  description: 'Open privacy settings',
2130
2482
  icon: PREFERENCES_ICON,
2131
2483
  onClick: ()=>{
2132
- const opener = getPreferenceCenterOpener();
2484
+ const opener = getPreferenceCenterOpener(namespace);
2133
2485
  if (opener) opener();
2134
2486
  }
2135
2487
  },
@@ -2185,6 +2537,7 @@ function createPanel(options) {
2185
2537
  let panelElement = null;
2186
2538
  let backdropElement = null;
2187
2539
  let contentContainer = null;
2540
+ let footerElement = null;
2188
2541
  function createPanelElement() {
2189
2542
  const corner = draggable?.getCorner() ?? stateManager.getState().position;
2190
2543
  const positionClass = getPositionClass(corner);
@@ -2209,7 +2562,7 @@ function createPanel(options) {
2209
2562
  }));
2210
2563
  return logoWrapper;
2211
2564
  })(),
2212
- span({
2565
+ renderer_span({
2213
2566
  text: 'c15t DevTools'
2214
2567
  })
2215
2568
  ]
@@ -2236,36 +2589,59 @@ function createPanel(options) {
2236
2589
  contentContainer = renderer_div({
2237
2590
  className: styles_panel_module.content
2238
2591
  });
2239
- const isConnected = storeConnector.isConnected();
2240
- const footer = renderer_div({
2241
- className: styles_panel_module.footer,
2242
- children: [
2243
- renderer_div({
2244
- className: styles_panel_module.footerStatus,
2245
- children: [
2246
- span({
2247
- className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2248
- }),
2249
- span({
2250
- text: isConnected ? 'Connected' : 'Disconnected'
2251
- })
2252
- ]
2253
- }),
2254
- span({
2255
- text: 'v1.8.3'
2256
- })
2257
- ]
2592
+ footerElement = renderer_div({
2593
+ className: styles_panel_module.footer
2258
2594
  });
2595
+ updateFooter();
2259
2596
  panel.appendChild(header);
2260
2597
  panel.appendChild(contentContainer);
2261
- panel.appendChild(footer);
2262
- if (isConnected) onRenderContent(contentContainer);
2598
+ panel.appendChild(footerElement);
2599
+ if (storeConnector.isConnected()) onRenderContent(contentContainer);
2263
2600
  else renderErrorState(contentContainer);
2264
2601
  return panel;
2265
2602
  }
2266
- function renderErrorState(container) {
2267
- clearElement(container);
2268
- const errorState = renderer_div({
2603
+ function updateFooter() {
2604
+ if (!footerElement) return;
2605
+ clearElement(footerElement);
2606
+ const isConnected = storeConnector.isConnected();
2607
+ const storeState = storeConnector.getState();
2608
+ const isLoading = storeState?.isLoadingConsentInfo ?? false;
2609
+ const diagnostics = storeConnector.getDiagnostics();
2610
+ const statusChildren = [
2611
+ renderer_span({
2612
+ className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2613
+ }),
2614
+ renderer_span({
2615
+ text: isConnected ? 'Connected' : 'Disconnected'
2616
+ })
2617
+ ];
2618
+ if (isLoading) statusChildren.push(renderer_span({
2619
+ className: styles_panel_module.footerMeta,
2620
+ text: '\u00b7 Fetching /init\u2026'
2621
+ }));
2622
+ else if (!isConnected) statusChildren.push(renderer_span({
2623
+ className: styles_panel_module.footerMeta,
2624
+ text: `· ${diagnostics.namespace} · retry ${diagnostics.reconnectAttempts} · next ${formatRetryDelay(diagnostics.nextRetryInMs)}`
2625
+ }));
2626
+ footerElement.appendChild(renderer_div({
2627
+ className: styles_panel_module.footerStatus,
2628
+ children: statusChildren
2629
+ }));
2630
+ if (!isConnected) footerElement.appendChild(renderer_button({
2631
+ className: styles_panel_module.inlineActionButton,
2632
+ text: 'Retry',
2633
+ onClick: ()=>{
2634
+ storeConnector.retryConnection();
2635
+ }
2636
+ }));
2637
+ footerElement.appendChild(renderer_span({
2638
+ text: `v${version}`
2639
+ }));
2640
+ }
2641
+ function renderErrorState(container) {
2642
+ clearElement(container);
2643
+ const diagnostics = storeConnector.getDiagnostics();
2644
+ const errorState = renderer_div({
2269
2645
  className: styles_panel_module.errorState,
2270
2646
  children: [
2271
2647
  (()=>{
@@ -2285,6 +2661,30 @@ function createPanel(options) {
2285
2661
  renderer_div({
2286
2662
  className: styles_panel_module.errorMessage,
2287
2663
  text: 'c15t consent manager is not initialized. Make sure you have set up the ConsentManagerProvider in your app.'
2664
+ }),
2665
+ renderer_div({
2666
+ className: styles_panel_module.errorMessage,
2667
+ style: {
2668
+ marginTop: '4px',
2669
+ fontSize: 'var(--c15t-devtools-font-size-xs)'
2670
+ },
2671
+ text: `Namespace: ${diagnostics.namespace} · Retries: ${diagnostics.reconnectAttempts} · Next retry: ${formatRetryDelay(diagnostics.nextRetryInMs)}`
2672
+ }),
2673
+ diagnostics.lastError ? renderer_div({
2674
+ className: styles_panel_module.errorMessage,
2675
+ style: {
2676
+ marginTop: '4px',
2677
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
2678
+ color: 'var(--c15t-text-muted)'
2679
+ },
2680
+ text: `Last error: ${diagnostics.lastError}`
2681
+ }) : null,
2682
+ renderer_button({
2683
+ className: styles_panel_module.inlineActionButton,
2684
+ text: 'Retry Connection',
2685
+ onClick: ()=>{
2686
+ storeConnector.retryConnection();
2687
+ }
2288
2688
  })
2289
2689
  ]
2290
2690
  });
@@ -2320,6 +2720,7 @@ function createPanel(options) {
2320
2720
  panelElement = null;
2321
2721
  }
2322
2722
  contentContainer = null;
2723
+ footerElement = null;
2323
2724
  isAnimatingOut = false;
2324
2725
  floatingButton.style.display = '';
2325
2726
  stateManager.setOpen(false);
@@ -2338,9 +2739,14 @@ function createPanel(options) {
2338
2739
  update();
2339
2740
  });
2340
2741
  const unsubscribeStore = storeConnector.subscribe(()=>{
2742
+ updateFooter();
2341
2743
  if (contentContainer) if (storeConnector.isConnected()) onRenderContent(contentContainer);
2342
2744
  else renderErrorState(contentContainer);
2343
2745
  });
2746
+ const unsubscribeDiagnostics = storeConnector.subscribeDiagnostics(()=>{
2747
+ updateFooter();
2748
+ if (contentContainer && !storeConnector.isConnected()) renderErrorState(contentContainer);
2749
+ });
2344
2750
  container.appendChild(floatingButton);
2345
2751
  removePortal = createPortal(container);
2346
2752
  return {
@@ -2350,6 +2756,7 @@ function createPanel(options) {
2350
2756
  destroy: ()=>{
2351
2757
  unsubscribeState();
2352
2758
  unsubscribeStore();
2759
+ unsubscribeDiagnostics();
2353
2760
  if (dropdownMenu) {
2354
2761
  dropdownMenu.destroy();
2355
2762
  dropdownMenu = null;
@@ -2399,6 +2806,11 @@ const EVENTS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
2399
2806
  <path d="M12 20h9"></path>
2400
2807
  <path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path>
2401
2808
  </svg>`;
2809
+ const MORE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
2810
+ <circle cx="12" cy="5" r="1.75"></circle>
2811
+ <circle cx="12" cy="12" r="1.75"></circle>
2812
+ <circle cx="12" cy="19" r="1.75"></circle>
2813
+ </svg>`;
2402
2814
  const TABS = [
2403
2815
  {
2404
2816
  id: 'location',
@@ -2434,12 +2846,56 @@ const TABS = [
2434
2846
  function tabs_createTabs(options) {
2435
2847
  const { onTabChange, disabledTabs = [] } = options;
2436
2848
  let activeTab = options.activeTab;
2849
+ let isOverflowMenuOpen = false;
2850
+ let visibleTabIds = [];
2851
+ let hiddenTabIds = [];
2437
2852
  const tabButtons = new Map();
2853
+ const overflowButtons = new Map();
2438
2854
  const tabList = renderer_div({
2439
- className: styles_tabs_module.tabList,
2855
+ className: styles_tabs_module.tabList
2856
+ });
2857
+ const tabStrip = renderer_div({
2858
+ className: styles_tabs_module.tabStrip,
2440
2859
  role: 'tablist',
2441
2860
  ariaLabel: 'DevTools tabs'
2442
2861
  });
2862
+ tabList.appendChild(tabStrip);
2863
+ const overflowMenu = renderer_div({
2864
+ className: styles_tabs_module.overflowMenu,
2865
+ role: 'menu',
2866
+ ariaLabel: 'All tabs'
2867
+ });
2868
+ overflowMenu.dataset.state = 'closed';
2869
+ const overflowButton = renderer_button({
2870
+ className: styles_tabs_module.overflowButton,
2871
+ ariaLabel: 'More tabs',
2872
+ ariaExpanded: 'false',
2873
+ onClick: ()=>toggleOverflowMenu(),
2874
+ onKeyDown: (e)=>{
2875
+ if ('ArrowDown' === e.key || 'Enter' === e.key || ' ' === e.key) {
2876
+ e.preventDefault();
2877
+ openOverflowMenu();
2878
+ focusFirstEnabledOverflowItem();
2879
+ }
2880
+ }
2881
+ });
2882
+ overflowButton.setAttribute('aria-haspopup', 'menu');
2883
+ const overflowIcon = renderer_div({
2884
+ className: styles_tabs_module.overflowButtonIcon
2885
+ });
2886
+ overflowIcon.appendChild(createSvgElement(MORE_ICON, {
2887
+ width: 14,
2888
+ height: 14
2889
+ }));
2890
+ overflowButton.appendChild(overflowIcon);
2891
+ const overflowContainer = renderer_div({
2892
+ className: styles_tabs_module.overflowContainer,
2893
+ children: [
2894
+ overflowButton,
2895
+ overflowMenu
2896
+ ]
2897
+ });
2898
+ tabList.appendChild(overflowContainer);
2443
2899
  for (const tab of TABS){
2444
2900
  const isActive = tab.id === activeTab;
2445
2901
  const isDisabled = disabledTabs.includes(tab.id);
@@ -2453,6 +2909,7 @@ function tabs_createTabs(options) {
2453
2909
  disabled: isDisabled,
2454
2910
  onClick: ()=>{
2455
2911
  if (!isDisabled) {
2912
+ closeOverflowMenu();
2456
2913
  setActiveTab(tab.id);
2457
2914
  onTabChange(tab.id);
2458
2915
  }
@@ -2469,11 +2926,188 @@ function tabs_createTabs(options) {
2469
2926
  tabButton.appendChild(iconWrapper);
2470
2927
  tabButton.appendChild(document.createTextNode(tab.label));
2471
2928
  tabButtons.set(tab.id, tabButton);
2472
- tabList.appendChild(tabButton);
2929
+ tabStrip.appendChild(tabButton);
2930
+ const overflowItem = renderer_button({
2931
+ className: `${styles_tabs_module.overflowItem} ${isActive ? styles_tabs_module.overflowItemActive : ''} ${isDisabled ? styles_tabs_module.overflowItemDisabled : ''}`,
2932
+ role: 'menuitemradio',
2933
+ ariaChecked: isActive ? 'true' : 'false',
2934
+ disabled: isDisabled,
2935
+ onClick: ()=>{
2936
+ if (!isDisabled) {
2937
+ setActiveTab(tab.id);
2938
+ onTabChange(tab.id);
2939
+ closeOverflowMenu();
2940
+ tabButtons.get(tab.id)?.focus();
2941
+ }
2942
+ },
2943
+ onKeyDown: (e)=>handleOverflowKeyDown(e, tab.id)
2944
+ });
2945
+ const overflowItemIcon = renderer_div({
2946
+ className: styles_tabs_module.overflowItemIcon
2947
+ });
2948
+ overflowItemIcon.appendChild(createSvgElement(tab.icon, {
2949
+ width: 14,
2950
+ height: 14
2951
+ }));
2952
+ overflowItem.appendChild(overflowItemIcon);
2953
+ overflowItem.appendChild(document.createTextNode(tab.label));
2954
+ overflowButtons.set(tab.id, overflowItem);
2955
+ overflowMenu.appendChild(overflowItem);
2956
+ }
2957
+ function applyActiveState(tab) {
2958
+ for (const [tabId, tabButton] of tabButtons){
2959
+ const isActive = tabId === tab;
2960
+ if (styles_tabs_module.tabActive) tabButton.classList.toggle(styles_tabs_module.tabActive, isActive);
2961
+ tabButton.setAttribute('aria-selected', isActive ? 'true' : 'false');
2962
+ tabButton.tabIndex = isActive ? 0 : -1;
2963
+ }
2964
+ for (const [tabId, overflowItem] of overflowButtons){
2965
+ const isActive = tabId === tab;
2966
+ if (styles_tabs_module.overflowItemActive) overflowItem.classList.toggle(styles_tabs_module.overflowItemActive, isActive);
2967
+ overflowItem.setAttribute('aria-checked', isActive ? 'true' : 'false');
2968
+ }
2969
+ }
2970
+ function updateVisibleTabs() {
2971
+ const allTabIds = TABS.map((t)=>t.id);
2972
+ const iabEnabled = !disabledTabs.includes('iab');
2973
+ const preferredSecondTab = iabEnabled ? 'iab' : 'consents';
2974
+ const overflowSecondTab = iabEnabled ? 'consents' : 'iab';
2975
+ const showOverflowSecondTabInStrip = activeTab === overflowSecondTab;
2976
+ const stripSecondTab = showOverflowSecondTabInStrip ? overflowSecondTab : preferredSecondTab;
2977
+ const forcedOverflowTab = showOverflowSecondTabInStrip ? preferredSecondTab : overflowSecondTab;
2978
+ const layoutTabIds = [
2979
+ 'location',
2980
+ stripSecondTab,
2981
+ "scripts",
2982
+ 'actions',
2983
+ 'events',
2984
+ forcedOverflowTab
2985
+ ];
2986
+ const forcedOverflowTabIds = new Set();
2987
+ forcedOverflowTabIds.add(forcedOverflowTab);
2988
+ for (const [index, tabId] of layoutTabIds.entries()){
2989
+ const tabButton = tabButtons.get(tabId);
2990
+ if (tabButton) tabButton.style.order = String(index);
2991
+ const overflowItem = overflowButtons.get(tabId);
2992
+ if (overflowItem) overflowItem.style.order = String(index);
2993
+ }
2994
+ for (const tabId of allTabIds){
2995
+ const tabButton = tabButtons.get(tabId);
2996
+ if (tabButton && styles_tabs_module.tabHidden) tabButton.classList.remove(styles_tabs_module.tabHidden);
2997
+ }
2998
+ if (styles_tabs_module.overflowContainerHidden) overflowContainer.classList.remove(styles_tabs_module.overflowContainerHidden);
2999
+ const stripGap = Number.parseFloat(getComputedStyle(tabStrip).gap || '0');
3000
+ const calculateVisibleTabs = (availableWidth)=>{
3001
+ if (availableWidth <= 0) return [];
3002
+ const nextVisible = [];
3003
+ let usedWidth = 0;
3004
+ for (const tabId of layoutTabIds){
3005
+ if (forcedOverflowTabIds.has(tabId)) continue;
3006
+ const tabButton = tabButtons.get(tabId);
3007
+ if (!tabButton) continue;
3008
+ const width = tabButton.getBoundingClientRect().width;
3009
+ const nextUsed = 0 === nextVisible.length ? width : usedWidth + stripGap + width;
3010
+ if (nextUsed <= availableWidth) {
3011
+ nextVisible.push(tabId);
3012
+ usedWidth = nextUsed;
3013
+ } else break;
3014
+ }
3015
+ return nextVisible;
3016
+ };
3017
+ const measureStripWidth = ()=>tabStrip.getBoundingClientRect().width;
3018
+ const showOverflowContainer = ()=>{
3019
+ if (styles_tabs_module.overflowContainerHidden) overflowContainer.classList.remove(styles_tabs_module.overflowContainerHidden);
3020
+ };
3021
+ const hideOverflowContainer = ()=>{
3022
+ if (styles_tabs_module.overflowContainerHidden) overflowContainer.classList.add(styles_tabs_module.overflowContainerHidden);
3023
+ };
3024
+ const measureVisibleWidth = (tabIds)=>{
3025
+ let width = 0;
3026
+ for (const [index, tabId] of tabIds.entries()){
3027
+ const tabButton = tabButtons.get(tabId);
3028
+ if (tabButton) {
3029
+ width += tabButton.getBoundingClientRect().width;
3030
+ if (index > 0) width += stripGap;
3031
+ }
3032
+ }
3033
+ return width;
3034
+ };
3035
+ if (0 === forcedOverflowTabIds.size) {
3036
+ hideOverflowContainer();
3037
+ const visibleWithoutOverflow = calculateVisibleTabs(measureStripWidth());
3038
+ if (visibleWithoutOverflow.length === layoutTabIds.length) visibleTabIds = visibleWithoutOverflow;
3039
+ else {
3040
+ showOverflowContainer();
3041
+ visibleTabIds = calculateVisibleTabs(measureStripWidth());
3042
+ }
3043
+ } else {
3044
+ showOverflowContainer();
3045
+ const withOverflow = calculateVisibleTabs(measureStripWidth());
3046
+ visibleTabIds = withOverflow.length > 0 ? withOverflow : [
3047
+ activeTab
3048
+ ];
3049
+ }
3050
+ if (!visibleTabIds.includes(activeTab) && !disabledTabs.includes(activeTab)) if (visibleTabIds.length > 0) visibleTabIds[visibleTabIds.length - 1] = activeTab;
3051
+ else visibleTabIds = [
3052
+ activeTab
3053
+ ];
3054
+ visibleTabIds = [
3055
+ ...new Set(visibleTabIds)
3056
+ ];
3057
+ const maxStripWidth = measureStripWidth();
3058
+ while(visibleTabIds.length > 1 && measureVisibleWidth(visibleTabIds) > maxStripWidth + 0.5){
3059
+ let removeIndex = visibleTabIds.length - 1;
3060
+ if (visibleTabIds[removeIndex] === activeTab) removeIndex = Math.max(0, removeIndex - 1);
3061
+ visibleTabIds.splice(removeIndex, 1);
3062
+ }
3063
+ hiddenTabIds = layoutTabIds.filter((tabId)=>!visibleTabIds.includes(tabId) || forcedOverflowTabIds.has(tabId) && tabId !== activeTab);
3064
+ for (const tabId of allTabIds){
3065
+ const tabButton = tabButtons.get(tabId);
3066
+ if (tabButton) {
3067
+ if (styles_tabs_module.tabHidden) tabButton.classList.toggle(styles_tabs_module.tabHidden, hiddenTabIds.includes(tabId));
3068
+ }
3069
+ }
3070
+ for (const tabId of allTabIds){
3071
+ const overflowItem = overflowButtons.get(tabId);
3072
+ if (overflowItem) {
3073
+ if (styles_tabs_module.overflowItemHidden) overflowItem.classList.toggle(styles_tabs_module.overflowItemHidden, !hiddenTabIds.includes(tabId));
3074
+ }
3075
+ }
3076
+ if (styles_tabs_module.overflowContainerHidden) overflowContainer.classList.toggle(styles_tabs_module.overflowContainerHidden, 0 === hiddenTabIds.length);
3077
+ if (0 === hiddenTabIds.length) closeOverflowMenu();
3078
+ }
3079
+ function focusFirstEnabledOverflowItem() {
3080
+ const firstEnabled = hiddenTabIds.find((tabId)=>!disabledTabs.includes(tabId));
3081
+ if (firstEnabled) overflowButtons.get(firstEnabled)?.focus();
3082
+ }
3083
+ function openOverflowMenu() {
3084
+ if (isOverflowMenuOpen || 0 === hiddenTabIds.length) return;
3085
+ isOverflowMenuOpen = true;
3086
+ overflowMenu.dataset.state = 'open';
3087
+ overflowButton.setAttribute('aria-expanded', 'true');
3088
+ document.addEventListener('click', handleOutsideClick);
3089
+ document.addEventListener('keydown', handleEscapeKey);
3090
+ }
3091
+ function closeOverflowMenu() {
3092
+ if (!isOverflowMenuOpen) return;
3093
+ isOverflowMenuOpen = false;
3094
+ overflowMenu.dataset.state = 'closed';
3095
+ overflowButton.setAttribute('aria-expanded', 'false');
3096
+ document.removeEventListener('click', handleOutsideClick);
3097
+ document.removeEventListener('keydown', handleEscapeKey);
3098
+ }
3099
+ function toggleOverflowMenu() {
3100
+ if (isOverflowMenuOpen) closeOverflowMenu();
3101
+ else openOverflowMenu();
3102
+ }
3103
+ function handleOutsideClick(e) {
3104
+ if (!overflowContainer.contains(e.target)) closeOverflowMenu();
3105
+ }
3106
+ function handleEscapeKey(e) {
3107
+ if ('Escape' === e.key) closeOverflowMenu();
2473
3108
  }
2474
3109
  function handleKeyDown(e, currentTab) {
2475
- const tabIds = TABS.map((t)=>t.id);
2476
- const enabledTabIds = tabIds.filter((id)=>!disabledTabs.includes(id));
3110
+ const enabledTabIds = visibleTabIds.filter((tabId)=>!disabledTabs.includes(tabId));
2477
3111
  const currentIndex = enabledTabIds.indexOf(currentTab);
2478
3112
  let newIndex = currentIndex;
2479
3113
  switch(e.key){
@@ -2500,23 +3134,152 @@ function tabs_createTabs(options) {
2500
3134
  tabButtons.get(newTab)?.focus();
2501
3135
  }
2502
3136
  }
3137
+ function handleOverflowKeyDown(e, currentTab) {
3138
+ const enabledTabIds = hiddenTabIds.filter((tabId)=>!disabledTabs.includes(tabId));
3139
+ const currentIndex = enabledTabIds.indexOf(currentTab);
3140
+ if ('Escape' === e.key) {
3141
+ e.preventDefault();
3142
+ closeOverflowMenu();
3143
+ overflowButton.focus();
3144
+ return;
3145
+ }
3146
+ let newIndex = currentIndex;
3147
+ switch(e.key){
3148
+ case 'ArrowDown':
3149
+ newIndex = (currentIndex + 1) % enabledTabIds.length;
3150
+ break;
3151
+ case 'ArrowUp':
3152
+ newIndex = currentIndex > 0 ? currentIndex - 1 : enabledTabIds.length - 1;
3153
+ break;
3154
+ default:
3155
+ return;
3156
+ }
3157
+ e.preventDefault();
3158
+ const newTab = enabledTabIds[newIndex];
3159
+ if (newTab) overflowButtons.get(newTab)?.focus();
3160
+ }
2503
3161
  function setActiveTab(tab) {
2504
3162
  activeTab = tab;
2505
- for (const [tabId, tabButton] of tabButtons){
2506
- const isActive = tabId === tab;
2507
- if (styles_tabs_module.tabActive) tabButton.classList.toggle(styles_tabs_module.tabActive, isActive);
2508
- tabButton.setAttribute('aria-selected', isActive ? 'true' : 'false');
2509
- tabButton.tabIndex = isActive ? 0 : -1;
2510
- }
3163
+ applyActiveState(tab);
3164
+ updateVisibleTabs();
2511
3165
  }
3166
+ const handleWindowResize = ()=>{
3167
+ updateVisibleTabs();
3168
+ };
3169
+ let resizeObserver = null;
3170
+ if ('undefined' != typeof ResizeObserver) {
3171
+ resizeObserver = new ResizeObserver(()=>{
3172
+ updateVisibleTabs();
3173
+ });
3174
+ resizeObserver.observe(tabList);
3175
+ } else window.addEventListener('resize', handleWindowResize);
3176
+ applyActiveState(activeTab);
3177
+ requestAnimationFrame(()=>{
3178
+ updateVisibleTabs();
3179
+ });
2512
3180
  return {
2513
3181
  element: tabList,
2514
3182
  setActiveTab,
2515
3183
  destroy: ()=>{
3184
+ closeOverflowMenu();
3185
+ if (resizeObserver) {
3186
+ resizeObserver.disconnect();
3187
+ resizeObserver = null;
3188
+ } else window.removeEventListener('resize', handleWindowResize);
2516
3189
  tabButtons.clear();
3190
+ overflowButtons.clear();
2517
3191
  }
2518
3192
  };
2519
3193
  }
3194
+ function debug_bundle_createDebugBundle(payload) {
3195
+ const { namespace, devToolsState, connection, recentEvents, storeState } = payload;
3196
+ const bundle = {
3197
+ generatedAt: new Date().toISOString(),
3198
+ namespace,
3199
+ devToolsState,
3200
+ connection,
3201
+ recentEvents,
3202
+ storeState,
3203
+ overrides: storeState?.overrides ?? null,
3204
+ iab: storeState?.iab ? {
3205
+ tcString: (storeState?.iab).tcString ?? null,
3206
+ purposeCount: Object.keys((storeState?.iab).purposeConsents ?? {}).length,
3207
+ vendorCount: Object.keys((storeState?.iab).vendorConsents ?? {}).length
3208
+ } : null
3209
+ };
3210
+ return JSON.stringify(bundle, null, 2);
3211
+ }
3212
+ function debug_bundle_sanitizeStoreState(state) {
3213
+ if (!state) return null;
3214
+ try {
3215
+ return JSON.parse(JSON.stringify(state, (_key, value)=>'function' == typeof value ? void 0 : value));
3216
+ } catch {
3217
+ return {
3218
+ error: 'Unable to serialize store state'
3219
+ };
3220
+ }
3221
+ }
3222
+ function debug_bundle_downloadDebugBundle(content) {
3223
+ const blob = new Blob([
3224
+ content
3225
+ ], {
3226
+ type: 'application/json'
3227
+ });
3228
+ const url = URL.createObjectURL(blob);
3229
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3230
+ const anchor = document.createElement('a');
3231
+ anchor.href = url;
3232
+ anchor.download = `c15t-debug-bundle-${timestamp}.json`;
3233
+ anchor.click();
3234
+ URL.revokeObjectURL(url);
3235
+ }
3236
+ const DEVTOOLS_OVERRIDES_STORAGE_KEY = 'c15t-devtools-overrides';
3237
+ function normalizeStringValue(value) {
3238
+ if ('string' != typeof value) return;
3239
+ const normalized = value.trim();
3240
+ return normalized.length > 0 ? normalized : void 0;
3241
+ }
3242
+ function normalizeBooleanValue(value) {
3243
+ return 'boolean' == typeof value ? value : void 0;
3244
+ }
3245
+ function normalizeOverrides(value) {
3246
+ if (!value || 'object' != typeof value) return null;
3247
+ const source = value;
3248
+ const overrides = {
3249
+ country: normalizeStringValue(source.country),
3250
+ region: normalizeStringValue(source.region),
3251
+ language: normalizeStringValue(source.language),
3252
+ gpc: normalizeBooleanValue(source.gpc)
3253
+ };
3254
+ return hasPersistedOverrides(overrides) ? overrides : null;
3255
+ }
3256
+ function hasPersistedOverrides(overrides) {
3257
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3258
+ }
3259
+ function override_storage_loadPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3260
+ if ('undefined' == typeof window) return null;
3261
+ try {
3262
+ const stored = localStorage.getItem(storageKey);
3263
+ if (!stored) return null;
3264
+ const parsed = JSON.parse(stored);
3265
+ return normalizeOverrides(parsed);
3266
+ } catch {
3267
+ return null;
3268
+ }
3269
+ }
3270
+ function override_storage_persistOverrides(overrides, storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3271
+ if ('undefined' == typeof window) return;
3272
+ try {
3273
+ if (!hasPersistedOverrides(overrides)) return void localStorage.removeItem(storageKey);
3274
+ localStorage.setItem(storageKey, JSON.stringify(overrides));
3275
+ } catch {}
3276
+ }
3277
+ function override_storage_clearPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3278
+ if ('undefined' == typeof window) return;
3279
+ try {
3280
+ localStorage.removeItem(storageKey);
3281
+ } catch {}
3282
+ }
2520
3283
  var components_module = __webpack_require__("../../node_modules/.bun/@rsbuild+core@1.6.12/node_modules/@rsbuild/core/compiled/css-loader/index.js??ruleSet[1].rules[1].use[1]!builtin:lightningcss-loader??ruleSet[1].rules[1].use[2]!./src/styles/components.module.css");
2521
3284
  var components_module_options = {};
2522
3285
  components_module_options.styleTagTransform = styleTagTransform_default();
@@ -2553,7 +3316,7 @@ function createBadge(options) {
2553
3316
  info: styles_components_module.badgeInfo,
2554
3317
  neutral: styles_components_module.badgeNeutral
2555
3318
  }[variant];
2556
- return span({
3319
+ return renderer_span({
2557
3320
  className: `${styles_components_module.badge} ${variantClass}`,
2558
3321
  text
2559
3322
  });
@@ -2584,31 +3347,19 @@ function createButton(options) {
2584
3347
  btn.appendChild(document.createTextNode(text));
2585
3348
  return btn;
2586
3349
  }
2587
- function createListItem(options) {
2588
- const { title, description, actions = [] } = options;
2589
- const content = renderer_div({
2590
- className: styles_components_module.listItemContent,
2591
- children: [
2592
- span({
2593
- className: styles_components_module.listItemTitle,
2594
- text: title
2595
- }),
2596
- description ? span({
2597
- className: styles_components_module.listItemDescription,
2598
- text: description
2599
- }) : null
2600
- ]
2601
- });
2602
- const actionsContainer = renderer_div({
2603
- className: styles_components_module.listItemActions,
2604
- children: actions
2605
- });
2606
- return renderer_div({
2607
- className: styles_components_module.listItem,
2608
- children: [
2609
- content,
2610
- actionsContainer
2611
- ]
3350
+ function createInput(options) {
3351
+ const { value, placeholder, ariaLabel, small = false, onInput } = options;
3352
+ const sizeClass = small ? styles_components_module.inputSmall : '';
3353
+ return input({
3354
+ className: `${styles_components_module.input} ${sizeClass}`.trim(),
3355
+ type: 'text',
3356
+ value,
3357
+ placeholder,
3358
+ ariaLabel,
3359
+ onInput: (event)=>{
3360
+ const target = event.target;
3361
+ onInput?.(target?.value ?? '');
3362
+ }
2612
3363
  });
2613
3364
  }
2614
3365
  function createSection(options) {
@@ -2616,7 +3367,7 @@ function createSection(options) {
2616
3367
  const header = renderer_div({
2617
3368
  className: styles_components_module.sectionHeader,
2618
3369
  children: [
2619
- span({
3370
+ renderer_span({
2620
3371
  className: styles_components_module.sectionTitle,
2621
3372
  text: title
2622
3373
  }),
@@ -2636,11 +3387,11 @@ function createInfoRow(options) {
2636
3387
  return renderer_div({
2637
3388
  className: styles_components_module.infoRow,
2638
3389
  children: [
2639
- span({
3390
+ renderer_span({
2640
3391
  className: styles_components_module.infoLabel,
2641
3392
  text: label
2642
3393
  }),
2643
- span({
3394
+ renderer_span({
2644
3395
  className: styles_components_module.infoValue,
2645
3396
  text: value
2646
3397
  })
@@ -2660,7 +3411,7 @@ function createEmptyState(options) {
2660
3411
  }));
2661
3412
  children.push(iconWrapper);
2662
3413
  }
2663
- children.push(span({
3414
+ children.push(renderer_span({
2664
3415
  className: styles_components_module.emptyStateText,
2665
3416
  text
2666
3417
  }));
@@ -2680,7 +3431,7 @@ function createGrid(options) {
2680
3431
  function createGridCard(options) {
2681
3432
  const { title, action } = options;
2682
3433
  const children = [
2683
- span({
3434
+ renderer_span({
2684
3435
  className: styles_components_module.gridCardTitle,
2685
3436
  text: title
2686
3437
  })
@@ -2691,6 +3442,18 @@ function createGridCard(options) {
2691
3442
  children
2692
3443
  });
2693
3444
  }
3445
+ function createDisconnectedState(message = 'Store not connected') {
3446
+ return renderer_div({
3447
+ className: styles_components_module.disconnectedState,
3448
+ style: {
3449
+ padding: '24px',
3450
+ textAlign: 'center',
3451
+ color: 'var(--c15t-text-muted)',
3452
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
3453
+ },
3454
+ text: message
3455
+ });
3456
+ }
2694
3457
  const REFRESH_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2695
3458
  <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
2696
3459
  <path d="M21 3v5h-5"></path>
@@ -2718,19 +3481,11 @@ const TERMINAL_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 2
2718
3481
  <polyline points="4 17 10 11 4 5"></polyline>
2719
3482
  <line x1="12" y1="19" x2="20" y2="19"></line>
2720
3483
  </svg>`;
2721
- function actions_renderActionsPanel(container, options) {
2722
- const { getState, onResetConsents, onRefetchBanner, onShowBanner, onOpenPreferences, onCopyState } = options;
3484
+ function renderActionsPanel(container, options) {
3485
+ const { getState, onResetConsents, onRefetchBanner, onShowBanner, onOpenPreferences, onCopyState, onExportDebugBundle } = options;
2723
3486
  clearElement(container);
2724
3487
  const state = getState();
2725
- if (!state) return void container.appendChild(renderer_div({
2726
- style: {
2727
- padding: '24px',
2728
- textAlign: 'center',
2729
- color: 'var(--c15t-text-muted)',
2730
- fontSize: 'var(--c15t-devtools-font-size-sm)'
2731
- },
2732
- text: 'Store not connected'
2733
- }));
3488
+ if (!state) return void container.appendChild(createDisconnectedState());
2734
3489
  const actionCards = [
2735
3490
  createActionCard({
2736
3491
  icon: actions_EYE_ICON,
@@ -2751,6 +3506,12 @@ function actions_renderActionsPanel(container, options) {
2751
3506
  icon: COPY_ICON,
2752
3507
  label: 'Copy State',
2753
3508
  onClick: onCopyState
3509
+ }),
3510
+ createActionCard({
3511
+ icon: REFRESH_ICON,
3512
+ label: 'Export Debug',
3513
+ onClick: ()=>onExportDebugBundle?.(),
3514
+ disabled: !onExportDebugBundle
2754
3515
  })
2755
3516
  ];
2756
3517
  const grid = createGrid({
@@ -2788,7 +3549,7 @@ function actions_renderActionsPanel(container, options) {
2788
3549
  },
2789
3550
  children: [
2790
3551
  createIconWrapper(TERMINAL_ICON, 14),
2791
- span({
3552
+ renderer_span({
2792
3553
  style: {
2793
3554
  fontSize: 'var(--c15t-devtools-font-size-xs)',
2794
3555
  fontWeight: '600',
@@ -2811,10 +3572,10 @@ function actions_renderActionsPanel(container, options) {
2811
3572
  color: 'var(--c15t-text-muted)'
2812
3573
  },
2813
3574
  children: [
2814
- span({
3575
+ renderer_span({
2815
3576
  text: `window.${getNamespace(state)}.getState()`
2816
3577
  }),
2817
- span({
3578
+ renderer_span({
2818
3579
  text: 'window.__c15tDevTools.open()'
2819
3580
  })
2820
3581
  ]
@@ -2824,7 +3585,7 @@ function actions_renderActionsPanel(container, options) {
2824
3585
  container.appendChild(consoleSection);
2825
3586
  }
2826
3587
  function createActionCard(options) {
2827
- const { icon, label, onClick } = options;
3588
+ const { icon, label, onClick, disabled = false } = options;
2828
3589
  const card = renderer_div({
2829
3590
  className: styles_components_module.gridCard ?? '',
2830
3591
  style: {
@@ -2835,11 +3596,12 @@ function createActionCard(options) {
2835
3596
  gap: '6px',
2836
3597
  padding: '16px 8px',
2837
3598
  cursor: 'pointer',
2838
- transition: 'background-color var(--c15t-duration-fast) var(--c15t-easing)'
3599
+ transition: 'background-color var(--c15t-duration-fast) var(--c15t-easing)',
3600
+ opacity: disabled ? '0.55' : '1'
2839
3601
  },
2840
3602
  children: [
2841
3603
  createIconWrapper(icon, 20),
2842
- span({
3604
+ renderer_span({
2843
3605
  style: {
2844
3606
  fontSize: 'var(--c15t-devtools-font-size-xs)',
2845
3607
  fontWeight: '500',
@@ -2850,13 +3612,15 @@ function createActionCard(options) {
2850
3612
  })
2851
3613
  ]
2852
3614
  });
2853
- card.addEventListener('click', onClick);
2854
- card.addEventListener('mouseenter', ()=>{
2855
- card.style.backgroundColor = 'var(--c15t-surface-hover)';
2856
- });
2857
- card.addEventListener('mouseleave', ()=>{
2858
- card.style.backgroundColor = '';
2859
- });
3615
+ if (!disabled) {
3616
+ card.addEventListener('click', onClick);
3617
+ card.addEventListener('mouseenter', ()=>{
3618
+ card.style.backgroundColor = 'var(--c15t-surface-hover)';
3619
+ });
3620
+ card.addEventListener('mouseleave', ()=>{
3621
+ card.style.backgroundColor = '';
3622
+ });
3623
+ }
2860
3624
  return card;
2861
3625
  }
2862
3626
  function createIconWrapper(icon, size) {
@@ -2877,19 +3641,12 @@ function createIconWrapper(icon, size) {
2877
3641
  function getNamespace(state) {
2878
3642
  return state.config?.meta?.namespace || 'c15tStore';
2879
3643
  }
2880
- function consents_renderConsentsPanel(container, options) {
3644
+ const consentSearchByContainer = new WeakMap();
3645
+ function renderConsentsPanel(container, options) {
2881
3646
  const { getState, onConsentChange, onSave, onAcceptAll, onRejectAll, onReset } = options;
2882
3647
  clearElement(container);
2883
3648
  const state = getState();
2884
- if (!state) return void container.appendChild(renderer_div({
2885
- style: {
2886
- padding: '24px',
2887
- textAlign: 'center',
2888
- color: 'var(--c15t-text-muted)',
2889
- fontSize: 'var(--c15t-devtools-font-size-sm)'
2890
- },
2891
- text: 'Store not connected'
2892
- }));
3649
+ if (!state) return void container.appendChild(createDisconnectedState());
2893
3650
  const isIabMode = 'iab' === state.model;
2894
3651
  const savedConsents = state.consents || {};
2895
3652
  const selectedConsents = state.selectedConsents || {};
@@ -2903,15 +3660,40 @@ function consents_renderConsentsPanel(container, options) {
2903
3660
  ct.name,
2904
3661
  ct
2905
3662
  ]));
3663
+ const searchQuery = consentSearchByContainer.get(container) ?? '';
2906
3664
  const consentEntries = Object.entries(displayConsents);
2907
- if (0 === consentEntries.length) container.appendChild(renderer_div({
3665
+ const filteredConsentEntries = consentEntries.filter(([name])=>{
3666
+ if (!searchQuery) return true;
3667
+ const consentType = consentTypeMap.get(name);
3668
+ const displayName = consentType?.name || name;
3669
+ return `${name} ${displayName}`.toLowerCase().includes(searchQuery);
3670
+ });
3671
+ const showSearchInput = consentEntries.length > 4;
3672
+ if (showSearchInput) container.appendChild(renderer_div({
3673
+ style: {
3674
+ padding: '8px 0 10px'
3675
+ },
3676
+ children: [
3677
+ createInput({
3678
+ value: searchQuery,
3679
+ placeholder: 'Filter consents…',
3680
+ ariaLabel: 'Filter consents',
3681
+ small: true,
3682
+ onInput: (value)=>{
3683
+ consentSearchByContainer.set(container, value.trim().toLowerCase());
3684
+ renderConsentsPanel(container, options);
3685
+ }
3686
+ })
3687
+ ]
3688
+ }));
3689
+ if (0 === filteredConsentEntries.length) container.appendChild(renderer_div({
2908
3690
  style: {
2909
3691
  padding: '24px',
2910
3692
  textAlign: 'center',
2911
3693
  color: 'var(--c15t-devtools-text-muted)',
2912
3694
  fontSize: 'var(--c15t-devtools-font-size-sm)'
2913
3695
  },
2914
- text: 'No consents configured'
3696
+ text: 0 === consentEntries.length ? 'No consents configured' : 'No matching consents'
2915
3697
  }));
2916
3698
  else {
2917
3699
  if (isIabMode) {
@@ -2929,7 +3711,7 @@ function consents_renderConsentsPanel(container, options) {
2929
3711
  container.appendChild(iabNotice);
2930
3712
  }
2931
3713
  const gridCards = [];
2932
- for (const [name, value] of consentEntries){
3714
+ for (const [name, value] of filteredConsentEntries){
2933
3715
  const consentType = consentTypeMap.get(name);
2934
3716
  const isNecessary = 'necessary' === name;
2935
3717
  const displayName = consentType?.name || name;
@@ -2993,13 +3775,13 @@ function consents_renderConsentsPanel(container, options) {
2993
3775
  },
2994
3776
  children: [
2995
3777
  createButton({
2996
- text: 'All',
3778
+ text: 'Accept',
2997
3779
  variant: 'primary',
2998
3780
  small: true,
2999
3781
  onClick: onAcceptAll
3000
3782
  }),
3001
3783
  createButton({
3002
- text: 'None',
3784
+ text: 'Reject',
3003
3785
  variant: 'default',
3004
3786
  small: true,
3005
3787
  onClick: onRejectAll
@@ -3019,7 +3801,7 @@ function consents_renderConsentsPanel(container, options) {
3019
3801
  gap: '8px'
3020
3802
  },
3021
3803
  children: [
3022
- span({
3804
+ renderer_span({
3023
3805
  style: {
3024
3806
  fontSize: 'var(--c15t-devtools-font-size-xs)',
3025
3807
  color: 'var(--c15t-devtools-badge-warning)'
@@ -3033,7 +3815,7 @@ function consents_renderConsentsPanel(container, options) {
3033
3815
  onClick: onSave
3034
3816
  })
3035
3817
  ]
3036
- }) : span({
3818
+ }) : renderer_span({
3037
3819
  style: {
3038
3820
  fontSize: 'var(--c15t-devtools-font-size-xs)',
3039
3821
  color: 'var(--c15t-text-muted)'
@@ -3047,19 +3829,36 @@ function consents_renderConsentsPanel(container, options) {
3047
3829
  function formatConsentName(name) {
3048
3830
  return name.replace(/_/g, ' ').replace(/\b\w/g, (l)=>l.toUpperCase());
3049
3831
  }
3050
- function events_renderEventsPanel(container, options) {
3832
+ const panelStateByContainer = new WeakMap();
3833
+ function getPanelState(container) {
3834
+ const existing = panelStateByContainer.get(container);
3835
+ if (existing) return existing;
3836
+ const initialState = {
3837
+ activeFilter: 'all',
3838
+ selectedEventId: null,
3839
+ searchQuery: ''
3840
+ };
3841
+ panelStateByContainer.set(container, initialState);
3842
+ return initialState;
3843
+ }
3844
+ function renderEventsPanel(container, options) {
3051
3845
  const { getEvents, onClear } = options;
3846
+ const panelState = getPanelState(container);
3052
3847
  clearElement(container);
3053
- const events = getEvents();
3848
+ const allEvents = getEvents();
3849
+ const events = allEvents.filter((event)=>matchesFilter(event, panelState.activeFilter)).filter((event)=>matchesSearch(event, panelState.searchQuery));
3850
+ if (!events.some((event)=>event.id === panelState.selectedEventId)) panelState.selectedEventId = events[0]?.id ?? null;
3851
+ const selectedEvent = events.find((event)=>event.id === panelState.selectedEventId) ?? null;
3054
3852
  const header = renderer_div({
3055
3853
  style: {
3056
3854
  display: 'flex',
3057
3855
  alignItems: 'center',
3058
3856
  justifyContent: 'space-between',
3059
- padding: '12px 16px 8px'
3857
+ padding: '12px 16px 8px',
3858
+ gap: '8px'
3060
3859
  },
3061
3860
  children: [
3062
- span({
3861
+ renderer_span({
3063
3862
  style: {
3064
3863
  fontSize: 'var(--c15t-devtools-font-size-xs)',
3065
3864
  fontWeight: '600',
@@ -3067,44 +3866,153 @@ function events_renderEventsPanel(container, options) {
3067
3866
  textTransform: 'uppercase',
3068
3867
  letterSpacing: '0.5px'
3069
3868
  },
3070
- text: `Events (${events.length})`
3869
+ text: `Events (${events.length}/${allEvents.length})`
3071
3870
  }),
3072
- createButton({
3073
- text: 'Clear',
3074
- small: true,
3075
- onClick: onClear
3871
+ renderer_div({
3872
+ style: {
3873
+ display: 'flex',
3874
+ gap: '6px'
3875
+ },
3876
+ children: [
3877
+ createButton({
3878
+ text: 'Export',
3879
+ small: true,
3880
+ onClick: ()=>exportEvents(allEvents)
3881
+ }),
3882
+ createButton({
3883
+ text: 'Clear',
3884
+ small: true,
3885
+ onClick: ()=>{
3886
+ onClear();
3887
+ panelState.selectedEventId = null;
3888
+ renderEventsPanel(container, options);
3889
+ }
3890
+ })
3891
+ ]
3076
3892
  })
3077
3893
  ]
3078
3894
  });
3079
3895
  container.appendChild(header);
3896
+ container.appendChild(renderer_div({
3897
+ style: {
3898
+ display: 'flex',
3899
+ flexWrap: 'wrap',
3900
+ gap: '6px',
3901
+ padding: '0 16px 8px'
3902
+ },
3903
+ children: EVENT_FILTERS.map((filter)=>createFilterButton(filter, filter === panelState.activeFilter, ()=>{
3904
+ panelState.activeFilter = filter;
3905
+ panelState.selectedEventId = null;
3906
+ renderEventsPanel(container, options);
3907
+ }))
3908
+ }));
3909
+ container.appendChild(renderer_div({
3910
+ style: {
3911
+ padding: '0 16px 8px'
3912
+ },
3913
+ children: [
3914
+ createInput({
3915
+ value: panelState.searchQuery,
3916
+ placeholder: 'Search events…',
3917
+ ariaLabel: 'Search events',
3918
+ small: true,
3919
+ onInput: (value)=>{
3920
+ panelState.searchQuery = value.trim().toLowerCase();
3921
+ panelState.selectedEventId = null;
3922
+ renderEventsPanel(container, options);
3923
+ }
3924
+ })
3925
+ ]
3926
+ }));
3080
3927
  const eventList = renderer_div({
3081
3928
  style: {
3082
3929
  display: 'flex',
3083
3930
  flexDirection: 'column',
3084
3931
  gap: '4px',
3085
3932
  padding: '0 12px 12px',
3086
- maxHeight: '400px',
3933
+ maxHeight: '300px',
3087
3934
  overflowY: 'auto'
3088
3935
  }
3089
3936
  });
3090
- if (0 === events.length) {
3091
- const emptyState = renderer_div({
3092
- style: {
3093
- padding: '32px 16px',
3094
- textAlign: 'center',
3095
- color: 'var(--c15t-text-muted)',
3096
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3097
- },
3098
- text: 'No events recorded yet'
3099
- });
3100
- eventList.appendChild(emptyState);
3101
- } else for (const event of events){
3102
- const eventItem = createEventItem(event);
3103
- eventList.appendChild(eventItem);
3104
- }
3937
+ if (0 === events.length) eventList.appendChild(renderer_div({
3938
+ style: {
3939
+ padding: '20px 16px',
3940
+ textAlign: 'center',
3941
+ color: 'var(--c15t-text-muted)',
3942
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
3943
+ },
3944
+ text: 'No events match this filter'
3945
+ }));
3946
+ else for (const event of events)eventList.appendChild(createEventItem(event, event.id === panelState.selectedEventId, ()=>{
3947
+ panelState.selectedEventId = event.id;
3948
+ renderEventsPanel(container, options);
3949
+ }));
3105
3950
  container.appendChild(eventList);
3951
+ container.appendChild(createPayloadSection(selectedEvent));
3952
+ }
3953
+ const EVENT_FILTERS = [
3954
+ 'all',
3955
+ 'error',
3956
+ 'consent',
3957
+ 'network',
3958
+ 'iab'
3959
+ ];
3960
+ function createFilterButton(filter, active, onClick) {
3961
+ return createButton({
3962
+ text: filter.toUpperCase(),
3963
+ small: true,
3964
+ variant: active ? 'primary' : 'default',
3965
+ onClick
3966
+ });
3967
+ }
3968
+ function matchesFilter(event, filter) {
3969
+ if ('all' === filter) return true;
3970
+ if ('error' === filter) return 'error' === event.type;
3971
+ if ('consent' === filter) return 'consent_set' === event.type || 'consent_save' === event.type || 'consent_reset' === event.type;
3972
+ if ('network' === filter) return 'network' === event.type;
3973
+ return 'iab' === event.type;
3974
+ }
3975
+ function matchesSearch(event, query) {
3976
+ if (!query) return true;
3977
+ const haystack = `${event.type} ${event.message} ${JSON.stringify(event.data ?? {})}`;
3978
+ return haystack.toLowerCase().includes(query);
3979
+ }
3980
+ function createPayloadSection(event) {
3981
+ const payload = event?.data ? JSON.stringify(event.data, null, 2) : null;
3982
+ return renderer_div({
3983
+ style: {
3984
+ padding: '0 12px 12px'
3985
+ },
3986
+ children: [
3987
+ renderer_div({
3988
+ style: {
3989
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3990
+ fontWeight: '600',
3991
+ color: 'var(--c15t-text-muted)',
3992
+ textTransform: 'uppercase',
3993
+ letterSpacing: '0.5px',
3994
+ marginBottom: '6px'
3995
+ },
3996
+ text: 'Payload'
3997
+ }),
3998
+ renderer_div({
3999
+ className: styles_components_module.gridCard ?? '',
4000
+ style: {
4001
+ padding: '8px',
4002
+ fontFamily: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
4003
+ fontSize: '11px',
4004
+ color: 'var(--c15t-text-muted)',
4005
+ maxHeight: '140px',
4006
+ overflowY: 'auto',
4007
+ whiteSpace: 'pre-wrap',
4008
+ wordBreak: 'break-word'
4009
+ },
4010
+ text: payload || 'Select an event with payload data'
4011
+ })
4012
+ ]
4013
+ });
3106
4014
  }
3107
- function createEventItem(event) {
4015
+ function createEventItem(event, selected, onSelect) {
3108
4016
  const time = formatTime(event.timestamp);
3109
4017
  const icon = getEventIcon(event.type);
3110
4018
  const color = getEventColor(event.type);
@@ -3115,10 +4023,13 @@ function createEventItem(event) {
3115
4023
  alignItems: 'center',
3116
4024
  gap: '8px',
3117
4025
  padding: '6px 10px',
3118
- fontSize: 'var(--c15t-devtools-font-size-xs)'
4026
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
4027
+ cursor: 'pointer',
4028
+ borderColor: selected ? 'var(--c15t-devtools-badge-info, #3b82f6)' : 'var(--c15t-border)'
3119
4029
  },
4030
+ onClick: onSelect,
3120
4031
  children: [
3121
- span({
4032
+ renderer_span({
3122
4033
  style: {
3123
4034
  color,
3124
4035
  fontSize: '8px',
@@ -3126,7 +4037,7 @@ function createEventItem(event) {
3126
4037
  },
3127
4038
  text: icon
3128
4039
  }),
3129
- span({
4040
+ renderer_span({
3130
4041
  style: {
3131
4042
  color: 'var(--c15t-text-muted)',
3132
4043
  fontFamily: 'monospace',
@@ -3135,7 +4046,7 @@ function createEventItem(event) {
3135
4046
  },
3136
4047
  text: time
3137
4048
  }),
3138
- span({
4049
+ renderer_span({
3139
4050
  style: {
3140
4051
  color: 'var(--c15t-text)',
3141
4052
  flex: '1'
@@ -3145,6 +4056,21 @@ function createEventItem(event) {
3145
4056
  ]
3146
4057
  });
3147
4058
  }
4059
+ function exportEvents(events) {
4060
+ const json = JSON.stringify(events, null, 2);
4061
+ const blob = new Blob([
4062
+ json
4063
+ ], {
4064
+ type: 'application/json'
4065
+ });
4066
+ const url = URL.createObjectURL(blob);
4067
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
4068
+ const a = document.createElement('a');
4069
+ a.href = url;
4070
+ a.download = `c15t-events-${timestamp}.json`;
4071
+ a.click();
4072
+ URL.revokeObjectURL(url);
4073
+ }
3148
4074
  function formatTime(timestamp) {
3149
4075
  const date = new Date(timestamp);
3150
4076
  return date.toLocaleTimeString('en-US', {
@@ -3163,7 +4089,10 @@ function getEventIcon(type) {
3163
4089
  return '○';
3164
4090
  case 'error':
3165
4091
  return '✕';
3166
- case 'info':
4092
+ case 'network':
4093
+ return '◉';
4094
+ case 'iab':
4095
+ return '◆';
3167
4096
  default:
3168
4097
  return '○';
3169
4098
  }
@@ -3177,43 +4106,23 @@ function getEventColor(type) {
3177
4106
  return 'var(--c15t-devtools-badge-warning, #f59e0b)';
3178
4107
  case 'error':
3179
4108
  return 'var(--c15t-devtools-badge-error, #ef4444)';
3180
- case 'info':
4109
+ case 'network':
4110
+ return 'var(--c15t-devtools-badge-warning, #f59e0b)';
4111
+ case 'iab':
4112
+ return 'var(--c15t-devtools-badge-info, #3b82f6)';
3181
4113
  default:
3182
4114
  return 'var(--c15t-text-muted)';
3183
4115
  }
3184
4116
  }
3185
- function iab_renderIabPanel(container, options) {
3186
- const { getState, onReset } = options;
4117
+ const iabSearchByContainer = new WeakMap();
4118
+ function renderIabPanel(container, options) {
4119
+ const { getState, onSetPurposeConsent, onSetVendorConsent, onSetSpecialFeatureOptIn, onAcceptAll, onRejectAll, onSave, onReset } = options;
3187
4120
  clearElement(container);
3188
4121
  const state = getState();
3189
- if (!state) return void container.appendChild(renderer_div({
3190
- style: {
3191
- padding: '24px',
3192
- textAlign: 'center',
3193
- color: 'var(--c15t-text-muted)',
3194
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3195
- },
3196
- text: 'Store not connected'
3197
- }));
3198
- if ('iab' !== state.model) return void container.appendChild(renderer_div({
3199
- style: {
3200
- padding: '24px',
3201
- textAlign: 'center',
3202
- color: 'var(--c15t-text-muted)',
3203
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3204
- },
3205
- text: 'IAB TCF mode is not configured'
3206
- }));
4122
+ if (!state) return void container.appendChild(createDisconnectedState());
4123
+ if ('iab' !== state.model) return void container.appendChild(createDisconnectedState('IAB TCF mode is not configured'));
3207
4124
  const iabState = state.iab;
3208
- if (!iabState) return void container.appendChild(renderer_div({
3209
- style: {
3210
- padding: '24px',
3211
- textAlign: 'center',
3212
- color: 'var(--c15t-text-muted)',
3213
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3214
- },
3215
- text: 'IAB state not available'
3216
- }));
4125
+ if (!iabState) return void container.appendChild(createDisconnectedState('IAB state not available'));
3217
4126
  const tcString = iabState.tcString;
3218
4127
  const tcStringSection = createSection({
3219
4128
  title: 'TC String',
@@ -3245,9 +4154,30 @@ function iab_renderIabPanel(container, options) {
3245
4154
  });
3246
4155
  container.appendChild(tcStringSection);
3247
4156
  const gvl = iabState.gvl;
4157
+ const searchQuery = iabSearchByContainer.get(container) ?? '';
4158
+ container.appendChild(createSection({
4159
+ title: 'Filter',
4160
+ children: [
4161
+ createInput({
4162
+ value: searchQuery,
4163
+ placeholder: 'Filter purposes or vendors…',
4164
+ ariaLabel: 'Filter IAB purposes and vendors',
4165
+ small: true,
4166
+ onInput: (value)=>{
4167
+ iabSearchByContainer.set(container, value.trim().toLowerCase());
4168
+ renderIabPanel(container, options);
4169
+ }
4170
+ })
4171
+ ]
4172
+ }));
3248
4173
  const purposeConsents = iabState.purposeConsents || {};
3249
4174
  const purposes = gvl?.purposes || {};
3250
- const purposeEntries = Object.entries(purposeConsents);
4175
+ const purposeEntries = Object.entries(purposeConsents).filter(([purposeId])=>{
4176
+ if (!searchQuery) return true;
4177
+ const purposeInfo = purposes[purposeId];
4178
+ const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
4179
+ return `${purposeId} ${purposeName}`.toLowerCase().includes(searchQuery);
4180
+ });
3251
4181
  if (purposeEntries.length > 0) {
3252
4182
  const purposeList = renderer_div({
3253
4183
  style: {
@@ -3261,7 +4191,9 @@ function iab_renderIabPanel(container, options) {
3261
4191
  for (const [purposeId, consent] of purposeEntries){
3262
4192
  const purposeInfo = purposes[purposeId];
3263
4193
  const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
3264
- purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent)));
4194
+ purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent), (value)=>{
4195
+ onSetPurposeConsent(Number(purposeId), value);
4196
+ }));
3265
4197
  }
3266
4198
  const purposesSection = createSection({
3267
4199
  title: `Purposes (${purposeEntries.length})`,
@@ -3273,7 +4205,12 @@ function iab_renderIabPanel(container, options) {
3273
4205
  }
3274
4206
  const specialFeatureOptIns = iabState.specialFeatureOptIns || {};
3275
4207
  const specialFeatures = gvl?.specialFeatures || {};
3276
- const specialFeatureEntries = Object.entries(specialFeatureOptIns);
4208
+ const specialFeatureEntries = Object.entries(specialFeatureOptIns).filter(([featureId])=>{
4209
+ if (!searchQuery) return true;
4210
+ const featureInfo = specialFeatures[featureId];
4211
+ const featureName = featureInfo?.name || `Special Feature ${featureId}`;
4212
+ return `${featureId} ${featureName}`.toLowerCase().includes(searchQuery);
4213
+ });
3277
4214
  if (specialFeatureEntries.length > 0) {
3278
4215
  const specialFeatureList = renderer_div({
3279
4216
  style: {
@@ -3287,7 +4224,9 @@ function iab_renderIabPanel(container, options) {
3287
4224
  for (const [featureId, optIn] of specialFeatureEntries){
3288
4225
  const featureInfo = specialFeatures[featureId];
3289
4226
  const featureName = featureInfo?.name || `Special Feature ${featureId}`;
3290
- specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn)));
4227
+ specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn), (value)=>{
4228
+ onSetSpecialFeatureOptIn(Number(featureId), value);
4229
+ }, 'feature'));
3291
4230
  }
3292
4231
  const specialFeaturesSection = createSection({
3293
4232
  title: `Special Features (${specialFeatureEntries.length})`,
@@ -3299,7 +4238,12 @@ function iab_renderIabPanel(container, options) {
3299
4238
  }
3300
4239
  const vendorConsents = iabState.vendorConsents || {};
3301
4240
  const vendors = gvl?.vendors || {};
3302
- const vendorEntries = Object.entries(vendorConsents);
4241
+ const vendorEntries = Object.entries(vendorConsents).filter(([vendorId])=>{
4242
+ if (!searchQuery) return true;
4243
+ const vendorInfo = vendors[vendorId];
4244
+ const vendorName = vendorInfo?.name || `Vendor ${vendorId}`;
4245
+ return `${vendorId} ${vendorName}`.toLowerCase().includes(searchQuery);
4246
+ });
3303
4247
  const iabVendors = [];
3304
4248
  const customVendors = [];
3305
4249
  for (const [vendorId, consent] of vendorEntries){
@@ -3327,7 +4271,9 @@ function iab_renderIabPanel(container, options) {
3327
4271
  overflowY: 'auto'
3328
4272
  }
3329
4273
  });
3330
- for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab'));
4274
+ for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab', (value)=>{
4275
+ onSetVendorConsent(Number(vendorId), value);
4276
+ }));
3331
4277
  const vendorsSection = createSection({
3332
4278
  title: `IAB Vendors (${iabVendors.length})`,
3333
4279
  children: [
@@ -3346,7 +4292,9 @@ function iab_renderIabPanel(container, options) {
3346
4292
  overflowY: 'auto'
3347
4293
  }
3348
4294
  });
3349
- for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom'));
4295
+ for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom', (value)=>{
4296
+ onSetVendorConsent(vendorId, value);
4297
+ }));
3350
4298
  const customVendorsSection = createSection({
3351
4299
  title: `Custom Vendors (${customVendors.length})`,
3352
4300
  children: [
@@ -3368,15 +4316,40 @@ function iab_renderIabPanel(container, options) {
3368
4316
  style: {
3369
4317
  display: 'flex',
3370
4318
  alignItems: 'center',
3371
- justifyContent: 'flex-end',
4319
+ justifyContent: 'space-between',
3372
4320
  padding: '12px 16px',
3373
4321
  marginTop: 'auto',
3374
4322
  borderTop: '1px solid var(--c15t-border)',
3375
4323
  backgroundColor: 'var(--c15t-surface)'
3376
4324
  },
3377
4325
  children: [
4326
+ renderer_div({
4327
+ style: {
4328
+ display: 'flex',
4329
+ gap: '6px'
4330
+ },
4331
+ children: [
4332
+ createButton({
4333
+ text: 'Accept All',
4334
+ variant: 'primary',
4335
+ small: true,
4336
+ onClick: onAcceptAll
4337
+ }),
4338
+ createButton({
4339
+ text: 'Reject All',
4340
+ small: true,
4341
+ onClick: onRejectAll
4342
+ }),
4343
+ createButton({
4344
+ text: 'Save',
4345
+ variant: 'primary',
4346
+ small: true,
4347
+ onClick: onSave
4348
+ })
4349
+ ]
4350
+ }),
3378
4351
  createButton({
3379
- text: 'Reset All',
4352
+ text: 'Reset',
3380
4353
  variant: 'danger',
3381
4354
  small: true,
3382
4355
  onClick: onReset
@@ -3385,7 +4358,7 @@ function iab_renderIabPanel(container, options) {
3385
4358
  });
3386
4359
  container.appendChild(footer);
3387
4360
  }
3388
- function createPurposeRow(id, name, consent) {
4361
+ function createPurposeRow(id, name, consent, onChange, ariaKind = 'purpose') {
3389
4362
  return renderer_div({
3390
4363
  style: {
3391
4364
  display: 'flex',
@@ -3396,7 +4369,7 @@ function createPurposeRow(id, name, consent) {
3396
4369
  borderBottom: '1px solid var(--c15t-border)'
3397
4370
  },
3398
4371
  children: [
3399
- span({
4372
+ renderer_span({
3400
4373
  style: {
3401
4374
  color: 'var(--c15t-text)',
3402
4375
  overflow: 'hidden',
@@ -3408,14 +4381,28 @@ function createPurposeRow(id, name, consent) {
3408
4381
  text: `${id}. ${name}`,
3409
4382
  title: name
3410
4383
  }),
3411
- createBadge({
3412
- text: consent ? '✓' : '✕',
3413
- variant: consent ? 'success' : 'error'
4384
+ renderer_div({
4385
+ style: {
4386
+ display: 'flex',
4387
+ alignItems: 'center',
4388
+ gap: '6px'
4389
+ },
4390
+ children: [
4391
+ createBadge({
4392
+ text: consent ? '✓' : '✕',
4393
+ variant: consent ? 'success' : 'error'
4394
+ }),
4395
+ createToggle({
4396
+ checked: consent,
4397
+ onChange,
4398
+ ariaLabel: `Toggle ${ariaKind} ${id}`
4399
+ })
4400
+ ]
3414
4401
  })
3415
4402
  ]
3416
4403
  });
3417
4404
  }
3418
- function createVendorRow(id, name, consent, type) {
4405
+ function createVendorRow(id, name, consent, type, onChange) {
3419
4406
  return renderer_div({
3420
4407
  style: {
3421
4408
  display: 'flex',
@@ -3436,7 +4423,7 @@ function createVendorRow(id, name, consent, type) {
3436
4423
  marginRight: '8px'
3437
4424
  },
3438
4425
  children: [
3439
- 'custom' === type ? span({
4426
+ 'custom' === type ? renderer_span({
3440
4427
  style: {
3441
4428
  fontSize: '9px',
3442
4429
  padding: '1px 4px',
@@ -3447,7 +4434,7 @@ function createVendorRow(id, name, consent, type) {
3447
4434
  },
3448
4435
  text: 'CUSTOM'
3449
4436
  }) : null,
3450
- span({
4437
+ renderer_span({
3451
4438
  style: {
3452
4439
  color: 'var(--c15t-text)',
3453
4440
  overflow: 'hidden',
@@ -3462,27 +4449,24 @@ function createVendorRow(id, name, consent, type) {
3462
4449
  createBadge({
3463
4450
  text: consent ? '✓' : '✕',
3464
4451
  variant: consent ? 'success' : 'error'
4452
+ }),
4453
+ createToggle({
4454
+ checked: consent,
4455
+ onChange,
4456
+ ariaLabel: `Toggle vendor ${id}`
3465
4457
  })
3466
4458
  ]
3467
4459
  });
3468
4460
  }
3469
4461
  function truncateText(text, maxLength) {
3470
4462
  if (text.length <= maxLength) return text;
3471
- return text.slice(0, maxLength - 3) + '...';
4463
+ return `${text.slice(0, maxLength - 3)}...`;
3472
4464
  }
3473
- function location_renderLocationPanel(container, options) {
3474
- const { getState, onSetOverrides, onClearOverrides } = options;
4465
+ function renderLocationPanel(container, options) {
4466
+ const { getState, onApplyOverrides, onClearOverrides } = options;
3475
4467
  clearElement(container);
3476
4468
  const state = getState();
3477
- if (!state) return void container.appendChild(renderer_div({
3478
- style: {
3479
- padding: '24px',
3480
- textAlign: 'center',
3481
- color: 'var(--c15t-text-muted)',
3482
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3483
- },
3484
- text: 'Store not connected'
3485
- }));
4469
+ if (!state) return void container.appendChild(createDisconnectedState());
3486
4470
  const locationInfo = state.locationInfo;
3487
4471
  const overrides = state.overrides;
3488
4472
  const translationConfig = state.translationConfig;
@@ -3492,145 +4476,230 @@ function location_renderLocationPanel(container, options) {
3492
4476
  createCompactInfoCard('Jurisdiction', locationInfo?.jurisdiction || '—'),
3493
4477
  createCompactInfoCard('Language', translationConfig?.defaultLanguage || '—')
3494
4478
  ];
4479
+ gridItems.push(createCompactInfoCard('GPC', getEffectiveGpcLabel(overrides?.gpc)));
3495
4480
  if (state.model) gridItems.push(createCompactInfoCard('Model', getModelLabel(state.model)));
3496
4481
  const locationGrid = createGrid({
3497
- columns: 2,
4482
+ columns: 3,
3498
4483
  children: gridItems
3499
4484
  });
3500
4485
  container.appendChild(locationGrid);
4486
+ const initialDraft = getDraftFromOverrides(overrides);
4487
+ let appliedOverrides = normalizeOverrideDraft(initialDraft);
4488
+ let isSubmitting = false;
4489
+ const countryField = createOverrideSelect({
4490
+ label: 'Country',
4491
+ selectOptions: COUNTRY_OPTIONS,
4492
+ value: initialDraft.country
4493
+ });
4494
+ const regionField = createOverrideInput({
4495
+ label: 'Region',
4496
+ placeholder: 'e.g., CA, NY, BE',
4497
+ value: initialDraft.region
4498
+ });
4499
+ const languageField = createOverrideInput({
4500
+ label: 'Language',
4501
+ placeholder: 'e.g., de, fr, en-US',
4502
+ value: initialDraft.language
4503
+ });
4504
+ const gpcField = createOverrideSelect({
4505
+ label: 'GPC',
4506
+ selectOptions: GPC_OPTIONS,
4507
+ value: initialDraft.gpc
4508
+ });
4509
+ const formStatus = renderer_span({
4510
+ className: styles_components_module.overrideStatus,
4511
+ text: 'In sync'
4512
+ });
4513
+ const applyButton = createButton({
4514
+ text: 'Apply',
4515
+ variant: 'primary',
4516
+ small: true,
4517
+ disabled: true,
4518
+ onClick: ()=>{
4519
+ applyDraft();
4520
+ }
4521
+ });
4522
+ const revertButton = createButton({
4523
+ text: 'Revert',
4524
+ small: true,
4525
+ disabled: true,
4526
+ onClick: ()=>{
4527
+ setDraftValues(getDraftFromOverrides(appliedOverrides));
4528
+ updateFormState();
4529
+ }
4530
+ });
4531
+ const clearButton = createButton({
4532
+ text: 'Clear',
4533
+ small: true,
4534
+ onClick: ()=>{
4535
+ clearDraftAndOverrides();
4536
+ }
4537
+ });
4538
+ const overrideFieldsGrid = renderer_div({
4539
+ style: {
4540
+ display: 'grid',
4541
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
4542
+ gap: '8px 10px'
4543
+ },
4544
+ children: [
4545
+ countryField.element,
4546
+ regionField.element,
4547
+ languageField.element,
4548
+ gpcField.element
4549
+ ]
4550
+ });
3501
4551
  const overrideSection = createSection({
3502
4552
  title: 'Override Settings',
3503
- actions: [
3504
- createButton({
3505
- text: 'Clear',
3506
- small: true,
3507
- onClick: onClearOverrides
3508
- })
3509
- ],
3510
4553
  children: [
3511
- createOverrideSelect({
3512
- label: 'Country',
3513
- selectOptions: COUNTRY_OPTIONS,
3514
- value: overrides?.country || '',
3515
- onChange: (value)=>onSetOverrides({
3516
- country: value || void 0
3517
- })
3518
- }),
3519
- createOverrideInput({
3520
- label: 'Region',
3521
- placeholder: 'e.g., CA, NY, BE',
3522
- value: overrides?.region || '',
3523
- onChange: (value)=>onSetOverrides({
3524
- region: value || void 0
3525
- })
4554
+ overrideFieldsGrid,
4555
+ renderer_span({
4556
+ className: styles_components_module.overrideHint,
4557
+ text: 'GPC override only affects opt-out or unregulated jurisdictions.'
3526
4558
  }),
3527
- createOverrideInput({
3528
- label: 'Language',
3529
- placeholder: 'e.g., de, fr, en',
3530
- value: overrides?.language || '',
3531
- onChange: (value)=>onSetOverrides({
3532
- language: value || void 0
3533
- })
4559
+ renderer_div({
4560
+ className: styles_components_module.overrideActions,
4561
+ children: [
4562
+ renderer_div({
4563
+ className: styles_components_module.overrideActionButtons,
4564
+ children: [
4565
+ revertButton,
4566
+ applyButton,
4567
+ clearButton
4568
+ ]
4569
+ }),
4570
+ formStatus
4571
+ ]
3534
4572
  })
3535
4573
  ]
3536
4574
  });
3537
4575
  container.appendChild(overrideSection);
3538
- const hasOverrides = overrides && (overrides.country || overrides.region || overrides.language);
3539
- if (hasOverrides) {
3540
- const overrideBanner = renderer_div({
3541
- style: {
3542
- padding: '8px 16px',
3543
- backgroundColor: 'var(--c15t-devtools-badge-info-bg)',
3544
- color: 'var(--c15t-devtools-badge-info)',
3545
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3546
- borderTop: '1px solid var(--c15t-devtools-border)'
3547
- },
3548
- text: 'Overrides are active. This may affect consent behavior.'
4576
+ countryField.control.addEventListener('change', updateFormState);
4577
+ regionField.control.addEventListener('input', updateFormState);
4578
+ languageField.control.addEventListener('input', updateFormState);
4579
+ gpcField.control.addEventListener('change', updateFormState);
4580
+ updateFormState();
4581
+ async function applyDraft() {
4582
+ if (isSubmitting) return;
4583
+ const draftOverrides = getDraftOverrides();
4584
+ if (overridesEqual(draftOverrides, appliedOverrides)) return;
4585
+ isSubmitting = true;
4586
+ updateFormState();
4587
+ try {
4588
+ await onApplyOverrides(draftOverrides);
4589
+ appliedOverrides = draftOverrides;
4590
+ } finally{
4591
+ isSubmitting = false;
4592
+ updateFormState();
4593
+ }
4594
+ }
4595
+ async function clearDraftAndOverrides() {
4596
+ if (isSubmitting) return;
4597
+ isSubmitting = true;
4598
+ updateFormState();
4599
+ try {
4600
+ await onClearOverrides();
4601
+ appliedOverrides = {};
4602
+ setDraftValues(getDraftFromOverrides(void 0));
4603
+ } finally{
4604
+ isSubmitting = false;
4605
+ updateFormState();
4606
+ }
4607
+ }
4608
+ function getDraftOverrides() {
4609
+ return normalizeOverrideDraft({
4610
+ country: countryField.control.value,
4611
+ region: regionField.control.value,
4612
+ language: languageField.control.value,
4613
+ gpc: gpcField.control.value
3549
4614
  });
3550
- container.appendChild(overrideBanner);
4615
+ }
4616
+ function setDraftValues(draft) {
4617
+ countryField.control.value = draft.country;
4618
+ regionField.control.value = draft.region;
4619
+ languageField.control.value = draft.language;
4620
+ gpcField.control.value = draft.gpc;
4621
+ }
4622
+ function updateFormState() {
4623
+ const draftOverrides = getDraftOverrides();
4624
+ const hasDraftChanges = !overridesEqual(draftOverrides, appliedOverrides);
4625
+ applyButton.disabled = !hasDraftChanges || isSubmitting;
4626
+ revertButton.disabled = !hasDraftChanges || isSubmitting;
4627
+ clearButton.disabled = isSubmitting;
4628
+ formStatus.textContent = isSubmitting ? 'Applying...' : hasDraftChanges ? 'Unsaved changes' : hasOverridesValue(appliedOverrides) ? 'Overrides active' : 'No overrides';
4629
+ if (styles_components_module.overrideStatusDirty) formStatus.classList.toggle(styles_components_module.overrideStatusDirty, !isSubmitting && hasDraftChanges);
3551
4630
  }
3552
4631
  }
3553
4632
  function createOverrideInput(options) {
3554
- const { label, placeholder, value, onChange } = options;
3555
- let debounceTimer = null;
4633
+ const { label, placeholder, value } = options;
3556
4634
  const inputField = input({
3557
4635
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3558
4636
  placeholder,
3559
- value,
3560
- onInput: (e)=>{
3561
- const target = e.target;
3562
- if (debounceTimer) clearTimeout(debounceTimer);
3563
- debounceTimer = setTimeout(()=>{
3564
- onChange(target.value);
3565
- }, 500);
3566
- }
3567
- });
3568
- return renderer_div({
3569
- style: {
3570
- display: 'flex',
3571
- alignItems: 'center',
3572
- justifyContent: 'space-between',
3573
- gap: '8px',
3574
- marginBottom: '8px'
3575
- },
3576
- children: [
3577
- renderer_div({
3578
- style: {
3579
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3580
- color: 'var(--c15t-devtools-text-muted)',
3581
- minWidth: '60px'
3582
- },
3583
- text: label
3584
- }),
3585
- renderer_div({
3586
- style: {
3587
- flex: '1'
3588
- },
3589
- children: [
3590
- inputField
3591
- ]
3592
- })
3593
- ]
4637
+ value
3594
4638
  });
4639
+ return {
4640
+ element: renderer_div({
4641
+ className: styles_components_module.overrideField,
4642
+ children: [
4643
+ renderer_span({
4644
+ className: styles_components_module.overrideLabel,
4645
+ text: label
4646
+ }),
4647
+ inputField
4648
+ ]
4649
+ }),
4650
+ control: inputField
4651
+ };
3595
4652
  }
3596
4653
  function createOverrideSelect(options) {
3597
- const { label, selectOptions, value, onChange } = options;
4654
+ const { label, selectOptions, value } = options;
3598
4655
  const selectField = renderer_select({
3599
4656
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3600
4657
  options: selectOptions,
3601
- selectedValue: value,
3602
- onChange: (e)=>{
3603
- const target = e.target;
3604
- onChange(target.value);
3605
- }
3606
- });
3607
- return renderer_div({
3608
- style: {
3609
- display: 'flex',
3610
- alignItems: 'center',
3611
- justifyContent: 'space-between',
3612
- gap: '8px',
3613
- marginBottom: '8px'
3614
- },
3615
- children: [
3616
- renderer_div({
3617
- style: {
3618
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3619
- color: 'var(--c15t-devtools-text-muted)',
3620
- minWidth: '60px'
3621
- },
3622
- text: label
3623
- }),
3624
- renderer_div({
3625
- style: {
3626
- flex: '1'
3627
- },
3628
- children: [
3629
- selectField
3630
- ]
3631
- })
3632
- ]
4658
+ selectedValue: value
3633
4659
  });
4660
+ return {
4661
+ element: renderer_div({
4662
+ className: styles_components_module.overrideField,
4663
+ children: [
4664
+ renderer_span({
4665
+ className: styles_components_module.overrideLabel,
4666
+ text: label
4667
+ }),
4668
+ selectField
4669
+ ]
4670
+ }),
4671
+ control: selectField
4672
+ };
4673
+ }
4674
+ function getDraftFromOverrides(overrides) {
4675
+ return {
4676
+ country: overrides?.country ?? '',
4677
+ region: overrides?.region ?? '',
4678
+ language: overrides?.language ?? '',
4679
+ gpc: overrides?.gpc === true ? 'true' : overrides?.gpc === false ? 'false' : ''
4680
+ };
4681
+ }
4682
+ function normalizeOverrideDraft(draft) {
4683
+ return {
4684
+ country: normalizeAlphaCode(draft.country),
4685
+ region: normalizeAlphaCode(draft.region),
4686
+ language: normalizeLanguageCode(draft.language),
4687
+ gpc: 'true' === draft.gpc ? true : 'false' === draft.gpc ? false : void 0
4688
+ };
4689
+ }
4690
+ function normalizeAlphaCode(value) {
4691
+ const normalized = value.trim().toUpperCase();
4692
+ return normalized || void 0;
4693
+ }
4694
+ function normalizeLanguageCode(value) {
4695
+ const normalized = value.trim();
4696
+ return normalized || void 0;
4697
+ }
4698
+ function overridesEqual(a, b) {
4699
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
4700
+ }
4701
+ function hasOverridesValue(overrides) {
4702
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3634
4703
  }
3635
4704
  const COUNTRY_OPTIONS = [
3636
4705
  {
@@ -3754,6 +4823,32 @@ const COUNTRY_OPTIONS = [
3754
4823
  label: 'ZA - South Africa'
3755
4824
  }
3756
4825
  ];
4826
+ const GPC_OPTIONS = [
4827
+ {
4828
+ value: '',
4829
+ label: '-- Browser Default --'
4830
+ },
4831
+ {
4832
+ value: 'true',
4833
+ label: 'Force On (Simulated)'
4834
+ },
4835
+ {
4836
+ value: 'false',
4837
+ label: 'Force Off (Simulated)'
4838
+ }
4839
+ ];
4840
+ function getEffectiveGpcLabel(gpcOverride) {
4841
+ if (true === gpcOverride) return 'On (Override)';
4842
+ if (false === gpcOverride) return 'Off (Override)';
4843
+ if ('undefined' == typeof window || 'undefined' == typeof navigator) return 'Unknown';
4844
+ try {
4845
+ const nav = navigator;
4846
+ const value = nav.globalPrivacyControl;
4847
+ return true === value || '1' === value ? 'Active' : 'Inactive';
4848
+ } catch {
4849
+ return 'Unknown';
4850
+ }
4851
+ }
3757
4852
  function getModelLabel(model) {
3758
4853
  switch(model){
3759
4854
  case 'opt-in':
@@ -3770,19 +4865,21 @@ function createCompactInfoCard(label, value) {
3770
4865
  return renderer_div({
3771
4866
  className: styles_components_module.gridCard ?? '',
3772
4867
  style: {
4868
+ padding: '6px 8px',
4869
+ minHeight: 'auto',
3773
4870
  flexDirection: 'column',
3774
4871
  alignItems: 'flex-start',
3775
- gap: '2px'
4872
+ gap: '1px'
3776
4873
  },
3777
4874
  children: [
3778
- span({
4875
+ renderer_span({
3779
4876
  style: {
3780
4877
  fontSize: 'var(--c15t-devtools-font-size-xs)',
3781
4878
  color: 'var(--c15t-text-muted)'
3782
4879
  },
3783
4880
  text: label
3784
4881
  }),
3785
- span({
4882
+ renderer_span({
3786
4883
  style: {
3787
4884
  fontSize: 'var(--c15t-font-size-sm)',
3788
4885
  fontWeight: '500',
@@ -3797,34 +4894,38 @@ const dismissedResources = new Set();
3797
4894
  function scanDOM(state) {
3798
4895
  const results = [];
3799
4896
  const configuredScripts = state.scripts || [];
3800
- const managedDomains = new Map();
4897
+ const managedResources = [];
3801
4898
  for (const script of configuredScripts)if (script.src) try {
3802
4899
  const url = new URL(script.src, window.location.origin);
3803
- if (url.hostname !== window.location.hostname) managedDomains.set(url.hostname, script.id);
4900
+ if (url.hostname !== window.location.hostname) managedResources.push({
4901
+ scriptId: script.id,
4902
+ domain: url.hostname,
4903
+ pathPrefix: normalizePathname(url.pathname)
4904
+ });
3804
4905
  } catch {}
3805
4906
  const scriptElements = document.querySelectorAll("script[src]");
3806
4907
  for (const el of scriptElements){
3807
4908
  const src = el.getAttribute('src');
3808
4909
  if (!src) continue;
3809
- const resource = checkResource(src, "script", managedDomains);
4910
+ const resource = checkResource(src, "script", managedResources);
3810
4911
  if (resource) results.push(resource);
3811
4912
  }
3812
4913
  const iframeElements = document.querySelectorAll('iframe[src]');
3813
4914
  for (const el of iframeElements){
3814
4915
  const src = el.getAttribute('src');
3815
4916
  if (!src) continue;
3816
- const resource = checkResource(src, 'iframe', managedDomains);
4917
+ const resource = checkResource(src, 'iframe', managedResources);
3817
4918
  if (resource) results.push(resource);
3818
4919
  }
3819
4920
  return results;
3820
4921
  }
3821
- function checkResource(src, type, managedDomains) {
4922
+ function checkResource(src, type, managedResources) {
3822
4923
  try {
3823
4924
  const url = new URL(src, window.location.origin);
3824
4925
  const domain = url.hostname;
3825
4926
  if (domain === window.location.hostname) return null;
3826
4927
  if ('data:' === url.protocol || 'blob:' === url.protocol) return null;
3827
- const managedBy = managedDomains.get(domain);
4928
+ const managedBy = findManagedScriptId(url, managedResources);
3828
4929
  const isManaged = Boolean(managedBy);
3829
4930
  return {
3830
4931
  type,
@@ -3836,6 +4937,21 @@ function checkResource(src, type, managedDomains) {
3836
4937
  } catch {}
3837
4938
  return null;
3838
4939
  }
4940
+ function findManagedScriptId(url, managedResources) {
4941
+ const domain = url.hostname;
4942
+ const path = normalizePathname(url.pathname);
4943
+ let bestMatch = null;
4944
+ for (const matcher of managedResources)if (matcher.domain === domain) {
4945
+ if ('/' === matcher.pathPrefix || path.startsWith(matcher.pathPrefix)) {
4946
+ if (!bestMatch || matcher.pathPrefix.length > bestMatch.pathPrefix.length) bestMatch = matcher;
4947
+ }
4948
+ }
4949
+ return bestMatch?.scriptId;
4950
+ }
4951
+ function normalizePathname(pathname) {
4952
+ const trimmed = pathname.trim();
4953
+ return trimmed.length > 0 ? trimmed : '/';
4954
+ }
3839
4955
  function createDomScannerSection(state) {
3840
4956
  let resultsContainer = null;
3841
4957
  let lastScanResults = [];
@@ -3939,21 +5055,21 @@ function createResourceRow(resource, variant, onDismiss) {
3939
5055
  borderBottom: '1px solid var(--c15t-border)'
3940
5056
  },
3941
5057
  children: [
3942
- span({
5058
+ renderer_span({
3943
5059
  style: {
3944
5060
  color: iconColor,
3945
5061
  flexShrink: '0'
3946
5062
  },
3947
5063
  text: icon
3948
5064
  }),
3949
- span({
5065
+ renderer_span({
3950
5066
  style: {
3951
5067
  color: 'var(--c15t-text-muted)',
3952
5068
  flexShrink: '0'
3953
5069
  },
3954
5070
  text: `${resource.type}:`
3955
5071
  }),
3956
- span({
5072
+ renderer_span({
3957
5073
  style: {
3958
5074
  fontWeight: '500',
3959
5075
  color: 'var(--c15t-text)',
@@ -3999,22 +5115,37 @@ const CODE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" f
3999
5115
  <polyline points="16 18 22 12 16 6"></polyline>
4000
5116
  <polyline points="8 6 2 12 8 18"></polyline>
4001
5117
  </svg>`;
4002
- function scripts_renderScriptsPanel(container, options) {
4003
- const { getState } = options;
5118
+ const scriptsSearchByContainer = new WeakMap();
5119
+ function renderScriptsPanel(container, options) {
5120
+ const { getState, getEvents } = options;
4004
5121
  clearElement(container);
4005
5122
  const state = getState();
4006
- if (!state) return void container.appendChild(renderer_div({
4007
- style: {
4008
- padding: '24px',
4009
- textAlign: 'center',
4010
- color: 'var(--c15t-text-muted)',
4011
- fontSize: 'var(--c15t-devtools-font-size-sm)'
4012
- },
4013
- text: 'Store not connected'
4014
- }));
5123
+ if (!state) return void container.appendChild(createDisconnectedState());
4015
5124
  const scripts = state.scripts || [];
4016
5125
  const loadedScripts = state.loadedScripts || {};
4017
5126
  const networkBlocker = state.networkBlocker;
5127
+ const events = getEvents?.() ?? [];
5128
+ const searchQuery = scriptsSearchByContainer.get(container) ?? '';
5129
+ const filteredScripts = scripts.filter((script)=>{
5130
+ if (!searchQuery) return true;
5131
+ const category = 'string' == typeof script.category ? script.category : JSON.stringify(script.category);
5132
+ return `${script.id} ${category}`.toLowerCase().includes(searchQuery);
5133
+ });
5134
+ if (scripts.length > 4) container.appendChild(createSection({
5135
+ title: 'Filter',
5136
+ children: [
5137
+ createInput({
5138
+ value: searchQuery,
5139
+ placeholder: "Filter scripts…",
5140
+ ariaLabel: "Filter scripts",
5141
+ small: true,
5142
+ onInput: (value)=>{
5143
+ scriptsSearchByContainer.set(container, value.trim().toLowerCase());
5144
+ renderScriptsPanel(container, options);
5145
+ }
5146
+ })
5147
+ ]
5148
+ }));
4018
5149
  if (0 === scripts.length) {
4019
5150
  const scriptsSection = createSection({
4020
5151
  title: 'Configured Scripts',
@@ -4031,10 +5162,19 @@ function scripts_renderScriptsPanel(container, options) {
4031
5162
  style: {
4032
5163
  display: 'flex',
4033
5164
  flexDirection: 'column',
4034
- gap: '4px'
5165
+ borderTop: '1px solid var(--c15t-border)',
5166
+ borderBottom: '1px solid var(--c15t-border)'
4035
5167
  }
4036
5168
  });
4037
- for (const script of scripts){
5169
+ if (0 === filteredScripts.length) scriptsList.appendChild(renderer_div({
5170
+ style: {
5171
+ padding: '10px 0',
5172
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
5173
+ color: 'var(--c15t-text-muted)'
5174
+ },
5175
+ text: "No matching scripts"
5176
+ }));
5177
+ for (const script of filteredScripts){
4038
5178
  const scriptId = script.id;
4039
5179
  const isLoaded = true === loadedScripts[scriptId];
4040
5180
  const category = script.category;
@@ -4058,17 +5198,64 @@ function scripts_renderScriptsPanel(container, options) {
4058
5198
  text: status.charAt(0).toUpperCase() + status.slice(1),
4059
5199
  variant: statusVariant
4060
5200
  });
4061
- const item = createListItem({
4062
- title: scriptId,
4063
- description: `Category: ${categoryDisplay}`,
4064
- actions: [
4065
- badge
5201
+ const row = renderer_div({
5202
+ style: {
5203
+ display: 'flex',
5204
+ alignItems: 'center',
5205
+ justifyContent: 'space-between',
5206
+ gap: '8px',
5207
+ padding: '8px 0',
5208
+ borderBottom: '1px solid var(--c15t-border)'
5209
+ },
5210
+ children: [
5211
+ renderer_div({
5212
+ style: {
5213
+ display: 'flex',
5214
+ flexDirection: 'column',
5215
+ gap: '2px',
5216
+ minWidth: '0',
5217
+ flex: '1'
5218
+ },
5219
+ children: [
5220
+ renderer_div({
5221
+ style: {
5222
+ fontSize: 'var(--c15t-font-size-sm)',
5223
+ fontWeight: '500',
5224
+ color: 'var(--c15t-text)',
5225
+ overflow: 'hidden',
5226
+ textOverflow: 'ellipsis',
5227
+ whiteSpace: 'nowrap'
5228
+ },
5229
+ text: scriptId
5230
+ }),
5231
+ renderer_div({
5232
+ style: {
5233
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
5234
+ color: 'var(--c15t-text-muted)',
5235
+ overflow: 'hidden',
5236
+ textOverflow: 'ellipsis',
5237
+ whiteSpace: 'nowrap'
5238
+ },
5239
+ text: `Category: ${categoryDisplay}`
5240
+ })
5241
+ ]
5242
+ }),
5243
+ renderer_div({
5244
+ style: {
5245
+ flexShrink: '0'
5246
+ },
5247
+ children: [
5248
+ badge
5249
+ ]
5250
+ })
4066
5251
  ]
4067
5252
  });
4068
- scriptsList.appendChild(item);
5253
+ scriptsList.appendChild(row);
4069
5254
  }
5255
+ const lastRow = scriptsList.lastElementChild;
5256
+ if (lastRow) lastRow.style.borderBottom = 'none';
4070
5257
  const scriptsSection = createSection({
4071
- title: `Configured Scripts (${scripts.length})`,
5258
+ title: `Configured Scripts (${filteredScripts.length}/${scripts.length})`,
4072
5259
  children: [
4073
5260
  scriptsList
4074
5261
  ]
@@ -4097,6 +5284,20 @@ function scripts_renderScriptsPanel(container, options) {
4097
5284
  ]
4098
5285
  });
4099
5286
  container.appendChild(networkSection);
5287
+ const blockedRequestEvents = events.filter((event)=>'network' === event.type);
5288
+ const networkEventsSection = createSection({
5289
+ title: `Blocked Requests (${blockedRequestEvents.length})`,
5290
+ children: 0 === blockedRequestEvents.length ? [
5291
+ renderer_div({
5292
+ style: {
5293
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
5294
+ color: 'var(--c15t-devtools-text-muted)'
5295
+ },
5296
+ text: 'No blocked network requests recorded in this session'
5297
+ })
5298
+ ] : createBlockedRequestContent(blockedRequestEvents)
5299
+ });
5300
+ container.appendChild(networkEventsSection);
4100
5301
  const loadedCount = Object.values(loadedScripts).filter(Boolean).length;
4101
5302
  const totalCount = scripts.length;
4102
5303
  const summarySection = createSection({
@@ -4122,14 +5323,82 @@ function scripts_renderScriptsPanel(container, options) {
4122
5323
  }
4123
5324
  function checkScriptConsent(state, category) {
4124
5325
  if (!category) return true;
5326
+ if ('function' == typeof state.has) try {
5327
+ return state.has(category);
5328
+ } catch {}
4125
5329
  if ('string' == typeof category) {
4126
5330
  const consents = state.consents || {};
4127
5331
  return true === consents[category];
4128
5332
  }
4129
5333
  return false;
4130
5334
  }
4131
- const STORAGE_KEYS = {
4132
- C15T: 'c15t',
5335
+ function createBlockedRequestContent(events) {
5336
+ const stats = new Map();
5337
+ for (const event of events){
5338
+ const ruleId = getEventRuleId(event) ?? 'unknown';
5339
+ stats.set(ruleId, (stats.get(ruleId) ?? 0) + 1);
5340
+ }
5341
+ const statsList = renderer_div({
5342
+ style: {
5343
+ display: 'flex',
5344
+ flexDirection: 'column',
5345
+ gap: '4px',
5346
+ marginBottom: '8px'
5347
+ },
5348
+ children: [
5349
+ ...stats.entries()
5350
+ ].sort((a, b)=>b[1] - a[1]).map(([ruleId, count])=>createInfoRow({
5351
+ label: 'unknown' === ruleId ? 'Unknown Rule' : `Rule: ${ruleId}`,
5352
+ value: `${count}`
5353
+ }))
5354
+ });
5355
+ const latestEvents = events.slice(0, 5);
5356
+ const latestList = renderer_div({
5357
+ style: {
5358
+ display: 'flex',
5359
+ flexDirection: 'column',
5360
+ gap: '4px'
5361
+ },
5362
+ children: latestEvents.map((event)=>createInfoRow({
5363
+ label: `${formatEventTime(event.timestamp)} ${getEventMethod(event)}`,
5364
+ value: scripts_truncateText(getEventUrl(event), 38)
5365
+ }))
5366
+ });
5367
+ return [
5368
+ statsList,
5369
+ latestList
5370
+ ];
5371
+ }
5372
+ function getEventRuleId(event) {
5373
+ const data = event.data;
5374
+ const rule = data?.rule;
5375
+ const ruleId = rule?.id ?? data?.ruleId;
5376
+ return 'string' == typeof ruleId || 'number' == typeof ruleId ? String(ruleId) : void 0;
5377
+ }
5378
+ function getEventMethod(event) {
5379
+ const data = event.data;
5380
+ const method = data?.method;
5381
+ return 'string' == typeof method ? method.toUpperCase() : 'REQ';
5382
+ }
5383
+ function getEventUrl(event) {
5384
+ const data = event.data;
5385
+ const url = data?.url;
5386
+ return 'string' == typeof url ? url : event.message;
5387
+ }
5388
+ function formatEventTime(timestamp) {
5389
+ return new Date(timestamp).toLocaleTimeString('en-US', {
5390
+ hour12: false,
5391
+ hour: '2-digit',
5392
+ minute: '2-digit',
5393
+ second: '2-digit'
5394
+ });
5395
+ }
5396
+ function scripts_truncateText(text, maxLength) {
5397
+ if (text.length <= maxLength) return text;
5398
+ return `${text.slice(0, maxLength - 3)}...`;
5399
+ }
5400
+ const STORAGE_KEYS = {
5401
+ C15T: 'c15t',
4133
5402
  PENDING_SYNC: 'c15t:pending-consent-sync',
4134
5403
  PENDING_SUBMISSIONS: 'c15t-pending-consent-submissions',
4135
5404
  EUCONSENT: 'euconsent-v2'
@@ -4153,7 +5422,7 @@ function clearAllLocalStorage() {
4153
5422
  localStorage.removeItem(STORAGE_KEYS.EUCONSENT);
4154
5423
  } catch {}
4155
5424
  }
4156
- async function reset_consents_resetAllConsents(store, stateManager) {
5425
+ async function resetAllConsents(store, stateManager) {
4157
5426
  const storeState = store.getState();
4158
5427
  storeState.resetConsents();
4159
5428
  clearAllCookies();
@@ -4164,7 +5433,222 @@ async function reset_consents_resetAllConsents(store, stateManager) {
4164
5433
  message: 'All consents reset (storage cleared)'
4165
5434
  });
4166
5435
  }
5436
+ function panel_renderer_createPanelRenderer(config) {
5437
+ const { storeConnector, stateManager, enableEventLogging = true, onPersistOverrides, onClearPersistedOverrides, onCopyState, onExportDebugBundle } = config;
5438
+ const getStoreState = ()=>storeConnector.getState();
5439
+ const logEvent = (type, message, data)=>{
5440
+ if (enableEventLogging) stateManager.addEvent({
5441
+ type,
5442
+ message,
5443
+ data
5444
+ });
5445
+ };
5446
+ const resetConsents = async ()=>{
5447
+ const store = storeConnector.getStore();
5448
+ if (store) await resetAllConsents(store, enableEventLogging ? stateManager : void 0);
5449
+ };
5450
+ const renderPanel = (container, tab)=>{
5451
+ switch(tab){
5452
+ case 'consents':
5453
+ renderConsentsPanel(container, {
5454
+ getState: getStoreState,
5455
+ onConsentChange: (name, value)=>{
5456
+ const store = storeConnector.getStore();
5457
+ if (store) {
5458
+ const consentName = String(name);
5459
+ store.getState().setSelectedConsent(consentName, value);
5460
+ logEvent('info', `${consentName} toggled to ${value} (not saved)`, {
5461
+ name: consentName,
5462
+ value
5463
+ });
5464
+ }
5465
+ },
5466
+ onSave: ()=>{
5467
+ const store = storeConnector.getStore();
5468
+ if (store) {
5469
+ store.getState().saveConsents('custom');
5470
+ logEvent('consent_save', 'Saved consent preferences');
5471
+ }
5472
+ },
5473
+ onAcceptAll: ()=>{
5474
+ const store = storeConnector.getStore();
5475
+ if (store) {
5476
+ store.getState().saveConsents('all');
5477
+ logEvent('consent_save', 'Accepted all consents');
5478
+ }
5479
+ },
5480
+ onRejectAll: ()=>{
5481
+ const store = storeConnector.getStore();
5482
+ if (store) {
5483
+ store.getState().saveConsents('necessary');
5484
+ logEvent('consent_save', 'Rejected all optional consents');
5485
+ }
5486
+ },
5487
+ onReset: resetConsents
5488
+ });
5489
+ break;
5490
+ case 'location':
5491
+ renderLocationPanel(container, {
5492
+ getState: getStoreState,
5493
+ onApplyOverrides: async (overrides)=>{
5494
+ const store = storeConnector.getStore();
5495
+ if (store) {
5496
+ await store.getState().setOverrides({
5497
+ country: overrides.country,
5498
+ region: overrides.region,
5499
+ language: overrides.language,
5500
+ gpc: overrides.gpc
5501
+ });
5502
+ logEvent('info', 'Overrides updated', {
5503
+ country: overrides.country,
5504
+ region: overrides.region,
5505
+ language: overrides.language,
5506
+ gpc: overrides.gpc
5507
+ });
5508
+ onPersistOverrides?.({
5509
+ country: overrides.country,
5510
+ region: overrides.region,
5511
+ language: overrides.language,
5512
+ gpc: overrides.gpc
5513
+ });
5514
+ }
5515
+ },
5516
+ onClearOverrides: async ()=>{
5517
+ const store = storeConnector.getStore();
5518
+ if (store) {
5519
+ await store.getState().setOverrides({
5520
+ country: void 0,
5521
+ region: void 0,
5522
+ language: void 0,
5523
+ gpc: void 0
5524
+ });
5525
+ logEvent('info', 'Overrides cleared');
5526
+ onClearPersistedOverrides?.();
5527
+ }
5528
+ }
5529
+ });
5530
+ break;
5531
+ case "scripts":
5532
+ renderScriptsPanel(container, {
5533
+ getState: getStoreState,
5534
+ getEvents: ()=>stateManager.getState().eventLog
5535
+ });
5536
+ break;
5537
+ case 'iab':
5538
+ renderIabPanel(container, {
5539
+ getState: getStoreState,
5540
+ onSetPurposeConsent: (purposeId, value)=>{
5541
+ const iab = storeConnector.getStore()?.getState().iab;
5542
+ if (!iab) return;
5543
+ iab.setPurposeConsent(purposeId, value);
5544
+ logEvent('iab', `IAB purpose ${purposeId} set to ${value}`);
5545
+ },
5546
+ onSetVendorConsent: (vendorId, value)=>{
5547
+ const iab = storeConnector.getStore()?.getState().iab;
5548
+ if (!iab) return;
5549
+ iab.setVendorConsent(vendorId, value);
5550
+ logEvent('iab', `IAB vendor ${vendorId} set to ${value}`);
5551
+ },
5552
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
5553
+ const iab = storeConnector.getStore()?.getState().iab;
5554
+ if (!iab) return;
5555
+ iab.setSpecialFeatureOptIn(featureId, value);
5556
+ logEvent('iab', `IAB feature ${featureId} set to ${value}`);
5557
+ },
5558
+ onAcceptAll: ()=>{
5559
+ const iab = storeConnector.getStore()?.getState().iab;
5560
+ if (!iab) return;
5561
+ iab.acceptAll();
5562
+ logEvent('iab', 'IAB accept all selected');
5563
+ },
5564
+ onRejectAll: ()=>{
5565
+ const iab = storeConnector.getStore()?.getState().iab;
5566
+ if (!iab) return;
5567
+ iab.rejectAll();
5568
+ logEvent('iab', 'IAB reject all selected');
5569
+ },
5570
+ onSave: ()=>{
5571
+ const iab = storeConnector.getStore()?.getState().iab;
5572
+ if (!iab) return;
5573
+ iab.save().then(()=>logEvent('iab', 'IAB preferences saved')).catch((error)=>{
5574
+ logEvent('error', `Failed to save IAB preferences: ${String(error)}`);
5575
+ });
5576
+ },
5577
+ onReset: resetConsents
5578
+ });
5579
+ break;
5580
+ case 'events':
5581
+ renderEventsPanel(container, {
5582
+ getEvents: ()=>stateManager.getState().eventLog,
5583
+ onClear: ()=>{
5584
+ stateManager.clearEventLog();
5585
+ logEvent('info', 'Event log cleared');
5586
+ }
5587
+ });
5588
+ break;
5589
+ case 'actions':
5590
+ renderActionsPanel(container, {
5591
+ getState: getStoreState,
5592
+ onResetConsents: resetConsents,
5593
+ onRefetchBanner: async ()=>{
5594
+ const store = storeConnector.getStore();
5595
+ if (store) {
5596
+ await store.getState().initConsentManager();
5597
+ logEvent('info', 'Banner data refetched');
5598
+ }
5599
+ },
5600
+ onShowBanner: ()=>{
5601
+ const store = storeConnector.getStore();
5602
+ if (store) {
5603
+ store.getState().setActiveUI('banner', {
5604
+ force: true
5605
+ });
5606
+ logEvent('info', 'Banner shown');
5607
+ }
5608
+ },
5609
+ onOpenPreferences: ()=>{
5610
+ const store = storeConnector.getStore();
5611
+ if (store) {
5612
+ store.getState().setActiveUI('dialog');
5613
+ logEvent('info', 'Preferences dialog opened');
5614
+ }
5615
+ },
5616
+ onCopyState: ()=>{
5617
+ const state = getStoreState();
5618
+ if (state) if (onCopyState) {
5619
+ const result = onCopyState(state);
5620
+ if (result instanceof Promise) result.then((ok)=>{
5621
+ logEvent(ok ? 'info' : 'error', ok ? 'State copied to clipboard' : 'Failed to copy state');
5622
+ }).catch(()=>{
5623
+ logEvent('error', 'Failed to copy state');
5624
+ });
5625
+ else logEvent(result ? 'info' : 'error', result ? 'State copied to clipboard' : 'Failed to copy state');
5626
+ } else navigator.clipboard.writeText(JSON.stringify(state, null, 2)).then(()=>{
5627
+ logEvent('info', 'State copied to clipboard');
5628
+ }).catch(()=>{
5629
+ logEvent('error', 'Failed to copy state');
5630
+ });
5631
+ },
5632
+ onExportDebugBundle: onExportDebugBundle ? ()=>{
5633
+ try {
5634
+ onExportDebugBundle();
5635
+ logEvent('info', 'Debug bundle exported');
5636
+ } catch {
5637
+ logEvent('error', 'Failed to export debug bundle');
5638
+ }
5639
+ } : void 0
5640
+ });
5641
+ break;
5642
+ }
5643
+ };
5644
+ return {
5645
+ renderPanel,
5646
+ getStoreState,
5647
+ resetConsents
5648
+ };
5649
+ }
4167
5650
  const STORAGE_KEY = 'c15t-devtools-events';
5651
+ const ACTIVE_TAB_STORAGE_KEY = 'c15t-devtools-active-tab';
4168
5652
  function loadPersistedEvents() {
4169
5653
  if ('undefined' == typeof window) return [];
4170
5654
  try {
@@ -4179,11 +5663,29 @@ function persistEvents(events) {
4179
5663
  sessionStorage.setItem(STORAGE_KEY, JSON.stringify(events));
4180
5664
  } catch {}
4181
5665
  }
5666
+ function isDevToolsTab(value) {
5667
+ return 'consents' === value || 'location' === value || "scripts" === value || 'iab' === value || 'events' === value || 'actions' === value;
5668
+ }
5669
+ function loadPersistedActiveTab() {
5670
+ if ('undefined' == typeof window) return null;
5671
+ try {
5672
+ const stored = localStorage.getItem(ACTIVE_TAB_STORAGE_KEY);
5673
+ if (isDevToolsTab(stored)) return stored;
5674
+ } catch {}
5675
+ return null;
5676
+ }
5677
+ function persistActiveTab(tab) {
5678
+ if ('undefined' == typeof window) return;
5679
+ try {
5680
+ localStorage.setItem(ACTIVE_TAB_STORAGE_KEY, tab);
5681
+ } catch {}
5682
+ }
4182
5683
  function state_manager_createStateManager(initialState = {}) {
4183
5684
  const persistedEvents = loadPersistedEvents();
5685
+ const persistedActiveTab = loadPersistedActiveTab();
4184
5686
  let state = {
4185
5687
  isOpen: false,
4186
- activeTab: 'location',
5688
+ activeTab: persistedActiveTab ?? 'location',
4187
5689
  position: 'bottom-right',
4188
5690
  isConnected: false,
4189
5691
  eventLog: persistedEvents,
@@ -4224,6 +5726,7 @@ function state_manager_createStateManager(initialState = {}) {
4224
5726
  setState({
4225
5727
  activeTab: tab
4226
5728
  });
5729
+ persistActiveTab(tab);
4227
5730
  },
4228
5731
  setPosition: (position)=>{
4229
5732
  setState({
@@ -4265,12 +5768,67 @@ function store_connector_createStoreConnector(options = {}) {
4265
5768
  const { namespace = 'c15tStore', onConnect, onStateChange, onDisconnect } = options;
4266
5769
  let store = null;
4267
5770
  let unsubscribe = null;
4268
- let pollInterval = null;
5771
+ let reconnectTimeout = null;
5772
+ let reconnectAttempts = 0;
5773
+ let hasNotifiedDisconnect = false;
4269
5774
  const listeners = new Set();
5775
+ const diagnosticsListeners = new Set();
5776
+ let diagnostics = {
5777
+ namespace,
5778
+ reconnectAttempts: 0,
5779
+ nextRetryInMs: null,
5780
+ lastError: null,
5781
+ isPolling: false,
5782
+ disconnectNotified: false
5783
+ };
5784
+ const INITIAL_RETRY_DELAY_MS = 100;
5785
+ const MAX_RETRY_DELAY_MS = 2000;
5786
+ const DISCONNECT_NOTIFY_ATTEMPTS = 5;
5787
+ function updateDiagnostics(partial, notify = true) {
5788
+ diagnostics = {
5789
+ ...diagnostics,
5790
+ ...partial
5791
+ };
5792
+ if (!notify) return;
5793
+ for (const listener of diagnosticsListeners)listener(diagnostics);
5794
+ }
5795
+ function clearReconnectTimer() {
5796
+ if (reconnectTimeout) {
5797
+ clearTimeout(reconnectTimeout);
5798
+ reconnectTimeout = null;
5799
+ updateDiagnostics({
5800
+ isPolling: false,
5801
+ nextRetryInMs: null
5802
+ });
5803
+ }
5804
+ }
5805
+ function resetReconnectState() {
5806
+ reconnectAttempts = 0;
5807
+ hasNotifiedDisconnect = false;
5808
+ updateDiagnostics({
5809
+ reconnectAttempts: 0,
5810
+ nextRetryInMs: null,
5811
+ lastError: null,
5812
+ disconnectNotified: false
5813
+ });
5814
+ }
5815
+ function notifyDisconnectedOnce() {
5816
+ if (hasNotifiedDisconnect) return;
5817
+ hasNotifiedDisconnect = true;
5818
+ updateDiagnostics({
5819
+ disconnectNotified: true
5820
+ });
5821
+ onDisconnect?.();
5822
+ }
4270
5823
  function tryConnect() {
4271
5824
  if ('undefined' == typeof window) return false;
4272
5825
  const storeInstance = window[namespace];
4273
5826
  if (storeInstance && 'function' == typeof storeInstance.getState) {
5827
+ if (store === storeInstance && unsubscribe) return true;
5828
+ if (unsubscribe) {
5829
+ unsubscribe();
5830
+ unsubscribe = null;
5831
+ }
4274
5832
  store = storeInstance;
4275
5833
  unsubscribe = store.subscribe((state)=>{
4276
5834
  onStateChange?.(state);
@@ -4278,30 +5836,41 @@ function store_connector_createStoreConnector(options = {}) {
4278
5836
  });
4279
5837
  const currentState = store.getState();
4280
5838
  onConnect?.(currentState, store);
4281
- if (pollInterval) {
4282
- clearInterval(pollInterval);
4283
- pollInterval = null;
4284
- }
5839
+ clearReconnectTimer();
5840
+ resetReconnectState();
5841
+ updateDiagnostics({
5842
+ lastError: null
5843
+ });
4285
5844
  return true;
4286
5845
  }
5846
+ updateDiagnostics({
5847
+ lastError: `Store "${namespace}" not found on window`
5848
+ });
4287
5849
  return false;
4288
5850
  }
5851
+ function scheduleReconnect(immediate = false) {
5852
+ if (store || reconnectTimeout) return;
5853
+ const delay = immediate ? 0 : Math.min(INITIAL_RETRY_DELAY_MS * 2 ** Math.min(reconnectAttempts, 5), MAX_RETRY_DELAY_MS);
5854
+ updateDiagnostics({
5855
+ isPolling: true,
5856
+ nextRetryInMs: delay,
5857
+ reconnectAttempts
5858
+ });
5859
+ reconnectTimeout = setTimeout(()=>{
5860
+ reconnectTimeout = null;
5861
+ reconnectAttempts++;
5862
+ updateDiagnostics({
5863
+ reconnectAttempts,
5864
+ nextRetryInMs: null
5865
+ });
5866
+ if (tryConnect()) return;
5867
+ if (reconnectAttempts >= DISCONNECT_NOTIFY_ATTEMPTS) notifyDisconnectedOnce();
5868
+ scheduleReconnect();
5869
+ }, delay);
5870
+ }
4289
5871
  function startPolling() {
4290
- if (pollInterval) return;
4291
5872
  if (tryConnect()) return;
4292
- let attempts = 0;
4293
- const maxAttempts = 50;
4294
- pollInterval = setInterval(()=>{
4295
- attempts++;
4296
- if (tryConnect()) return;
4297
- if (attempts >= maxAttempts) {
4298
- if (pollInterval) {
4299
- clearInterval(pollInterval);
4300
- pollInterval = null;
4301
- }
4302
- onDisconnect?.();
4303
- }
4304
- }, 100);
5873
+ scheduleReconnect(true);
4305
5874
  }
4306
5875
  startPolling();
4307
5876
  return {
@@ -4315,17 +5884,155 @@ function store_connector_createStoreConnector(options = {}) {
4315
5884
  listeners.delete(listener);
4316
5885
  };
4317
5886
  },
5887
+ getDiagnostics: ()=>diagnostics,
5888
+ subscribeDiagnostics: (listener)=>{
5889
+ diagnosticsListeners.add(listener);
5890
+ listener(diagnostics);
5891
+ return ()=>{
5892
+ diagnosticsListeners.delete(listener);
5893
+ };
5894
+ },
5895
+ retryConnection: ()=>{
5896
+ if (store) return;
5897
+ resetReconnectState();
5898
+ scheduleReconnect(true);
5899
+ },
4318
5900
  destroy: ()=>{
4319
- if (pollInterval) {
4320
- clearInterval(pollInterval);
4321
- pollInterval = null;
4322
- }
5901
+ clearReconnectTimer();
4323
5902
  if (unsubscribe) {
4324
5903
  unsubscribe();
4325
5904
  unsubscribe = null;
4326
5905
  }
4327
5906
  store = null;
4328
5907
  listeners.clear();
5908
+ diagnosticsListeners.clear();
5909
+ }
5910
+ };
5911
+ }
5912
+ const REGISTRY_KEY = '__c15tDevToolsInstrumentationRegistry';
5913
+ let fallbackRegistry = null;
5914
+ function getRegistry() {
5915
+ if ('undefined' == typeof window) {
5916
+ if (!fallbackRegistry) fallbackRegistry = new Map();
5917
+ return fallbackRegistry;
5918
+ }
5919
+ const host = window;
5920
+ const existing = host[REGISTRY_KEY];
5921
+ if (existing) return existing;
5922
+ const registry = new Map();
5923
+ host[REGISTRY_KEY] = registry;
5924
+ return registry;
5925
+ }
5926
+ function getBlockedRequestMessage(payload) {
5927
+ const data = payload;
5928
+ const method = 'string' == typeof data?.method ? data.method.toUpperCase() : 'REQUEST';
5929
+ const url = 'string' == typeof data?.url ? data.url : 'unknown-url';
5930
+ return `Network blocked: ${method} ${url}`;
5931
+ }
5932
+ function emitEvent(entry, event) {
5933
+ for (const listener of entry.listeners)listener(event);
5934
+ }
5935
+ function ensureNetworkBlockerWrapped(entry) {
5936
+ const blocker = entry.store.getState().networkBlocker;
5937
+ if (!blocker) return;
5938
+ if (blocker.onRequestBlocked === entry.wrappedNetworkBlockedCallback) return;
5939
+ entry.originalNetworkBlockedCallback = blocker.onRequestBlocked;
5940
+ entry.wrappedNetworkBlockedCallback = (payload)=>{
5941
+ emitEvent(entry, {
5942
+ type: 'network',
5943
+ message: getBlockedRequestMessage(payload),
5944
+ data: payload
5945
+ });
5946
+ if ('function' == typeof entry.originalNetworkBlockedCallback) entry.originalNetworkBlockedCallback(payload);
5947
+ };
5948
+ entry.store.getState().setNetworkBlocker({
5949
+ ...blocker,
5950
+ onRequestBlocked: entry.wrappedNetworkBlockedCallback
5951
+ });
5952
+ }
5953
+ function restoreInstrumentation(entry) {
5954
+ entry.stopWatchingStore?.();
5955
+ entry.stopWatchingStore = null;
5956
+ const state = entry.store.getState();
5957
+ state.setCallback('onBannerFetched', entry.originalCallbacks.onBannerFetched);
5958
+ state.setCallback('onConsentSet', entry.originalCallbacks.onConsentSet);
5959
+ state.setCallback('onError', entry.originalCallbacks.onError);
5960
+ state.setCallback('onBeforeConsentRevocationReload', entry.originalCallbacks.onBeforeConsentRevocationReload);
5961
+ const blocker = state.networkBlocker;
5962
+ if (blocker && blocker.onRequestBlocked === entry.wrappedNetworkBlockedCallback) state.setNetworkBlocker({
5963
+ ...blocker,
5964
+ onRequestBlocked: entry.originalNetworkBlockedCallback
5965
+ });
5966
+ entry.wrappedNetworkBlockedCallback = null;
5967
+ }
5968
+ function createInstrumentationEntry(store) {
5969
+ const entry = {
5970
+ store,
5971
+ listeners: new Set(),
5972
+ originalCallbacks: {
5973
+ ...store.getState().callbacks
5974
+ },
5975
+ originalNetworkBlockedCallback: store.getState().networkBlocker?.onRequestBlocked,
5976
+ wrappedNetworkBlockedCallback: null,
5977
+ stopWatchingStore: null
5978
+ };
5979
+ store.getState().setCallback('onBannerFetched', (payload)=>{
5980
+ const jurisdiction = payload.jurisdiction;
5981
+ emitEvent(entry, {
5982
+ type: 'info',
5983
+ message: `Banner fetched: ${String(jurisdiction)}`,
5984
+ data: payload
5985
+ });
5986
+ if ('function' == typeof entry.originalCallbacks.onBannerFetched) entry.originalCallbacks.onBannerFetched(payload);
5987
+ });
5988
+ store.getState().setCallback('onConsentSet', (payload)=>{
5989
+ emitEvent(entry, {
5990
+ type: 'consent_set',
5991
+ message: 'Consent preferences updated',
5992
+ data: payload
5993
+ });
5994
+ if ('function' == typeof entry.originalCallbacks.onConsentSet) entry.originalCallbacks.onConsentSet(payload);
5995
+ });
5996
+ store.getState().setCallback('onError', (payload)=>{
5997
+ const errorMessage = payload.error;
5998
+ emitEvent(entry, {
5999
+ type: 'error',
6000
+ message: `Error: ${String(errorMessage)}`,
6001
+ data: payload
6002
+ });
6003
+ if ('function' == typeof entry.originalCallbacks.onError) entry.originalCallbacks.onError(payload);
6004
+ });
6005
+ store.getState().setCallback('onBeforeConsentRevocationReload', (payload)=>{
6006
+ emitEvent(entry, {
6007
+ type: 'info',
6008
+ message: 'Consent revocation - page will reload',
6009
+ data: payload
6010
+ });
6011
+ if ('function' == typeof entry.originalCallbacks.onBeforeConsentRevocationReload) entry.originalCallbacks.onBeforeConsentRevocationReload(payload);
6012
+ });
6013
+ ensureNetworkBlockerWrapped(entry);
6014
+ entry.stopWatchingStore = store.subscribe(()=>{
6015
+ ensureNetworkBlockerWrapped(entry);
6016
+ });
6017
+ return entry;
6018
+ }
6019
+ function store_instrumentation_registerStoreInstrumentation(options) {
6020
+ const { namespace, store, onEvent } = options;
6021
+ const registry = getRegistry();
6022
+ let entry = registry.get(namespace);
6023
+ if (!entry || entry.store !== store) {
6024
+ if (entry) restoreInstrumentation(entry);
6025
+ entry = createInstrumentationEntry(store);
6026
+ registry.set(namespace, entry);
6027
+ }
6028
+ entry.listeners.add(onEvent);
6029
+ return ()=>{
6030
+ const current = registry.get(namespace);
6031
+ if (!current) return;
6032
+ current.listeners.delete(onEvent);
6033
+ if (0 === current.listeners.size) {
6034
+ restoreInstrumentation(current);
6035
+ registry.delete(namespace);
4329
6036
  }
4330
6037
  };
4331
6038
  }
@@ -4338,59 +6045,148 @@ tokens_options.domAPI = styleDomAPI_default();
4338
6045
  tokens_options.insertStyleElement = insertStyleElement_default();
4339
6046
  injectStylesIntoStyleTag_default()(tokens.A, tokens_options);
4340
6047
  tokens.A && tokens.A.locals && tokens.A.locals;
6048
+ const PANEL_HEIGHT_TRANSITION = 'height var(--c15t-duration-normal, 200ms) var(--c15t-easing, cubic-bezier(0.4, 0, 0.2, 1))';
6049
+ const PANEL_HEIGHT_TRANSITION_MS = 200;
6050
+ const PANEL_HEIGHT_TRANSITION_BUFFER_MS = 80;
6051
+ function normalizeOverridesForPersistence(overrides) {
6052
+ return {
6053
+ country: overrides?.country?.trim() || void 0,
6054
+ region: overrides?.region?.trim() || void 0,
6055
+ language: overrides?.language?.trim() || void 0,
6056
+ gpc: overrides?.gpc
6057
+ };
6058
+ }
6059
+ function persistedOverridesEqual(a, b) {
6060
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
6061
+ }
6062
+ function prefersReducedMotion() {
6063
+ return 'undefined' != typeof window && 'function' == typeof window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
6064
+ }
6065
+ function createPanelHeightAnimator() {
6066
+ let activePanel = null;
6067
+ let frameId = null;
6068
+ let timeoutId = null;
6069
+ let removeTransitionListener = null;
6070
+ function clearAnimationState() {
6071
+ if (null !== frameId) {
6072
+ window.cancelAnimationFrame(frameId);
6073
+ frameId = null;
6074
+ }
6075
+ if (null !== timeoutId) {
6076
+ clearTimeout(timeoutId);
6077
+ timeoutId = null;
6078
+ }
6079
+ if (removeTransitionListener) {
6080
+ removeTransitionListener();
6081
+ removeTransitionListener = null;
6082
+ }
6083
+ if (activePanel) {
6084
+ activePanel.style.height = '';
6085
+ activePanel.style.transition = '';
6086
+ activePanel.style.willChange = '';
6087
+ activePanel = null;
6088
+ }
6089
+ }
6090
+ function animate(panel, previousHeight) {
6091
+ if (!Number.isFinite(previousHeight) || prefersReducedMotion()) return;
6092
+ const nextHeight = panel.getBoundingClientRect().height;
6093
+ if (!Number.isFinite(nextHeight) || Math.abs(nextHeight - previousHeight) < 1) return;
6094
+ clearAnimationState();
6095
+ activePanel = panel;
6096
+ panel.style.height = `${previousHeight}px`;
6097
+ panel.style.willChange = 'height';
6098
+ panel.getBoundingClientRect();
6099
+ const handleTransitionEnd = (event)=>{
6100
+ const transitionEvent = event;
6101
+ if ('string' == typeof transitionEvent.propertyName && transitionEvent.propertyName && 'height' !== transitionEvent.propertyName) return;
6102
+ clearAnimationState();
6103
+ };
6104
+ panel.addEventListener('transitionend', handleTransitionEnd);
6105
+ removeTransitionListener = ()=>{
6106
+ panel.removeEventListener('transitionend', handleTransitionEnd);
6107
+ };
6108
+ frameId = window.requestAnimationFrame(()=>{
6109
+ frameId = null;
6110
+ panel.style.transition = PANEL_HEIGHT_TRANSITION;
6111
+ panel.style.height = `${nextHeight}px`;
6112
+ });
6113
+ timeoutId = setTimeout(()=>{
6114
+ clearAnimationState();
6115
+ }, PANEL_HEIGHT_TRANSITION_MS + PANEL_HEIGHT_TRANSITION_BUFFER_MS);
6116
+ }
6117
+ return {
6118
+ animate,
6119
+ destroy: clearAnimationState
6120
+ };
6121
+ }
6122
+ function createStateCopy(state) {
6123
+ return {
6124
+ consents: state.consents,
6125
+ selectedConsents: state.selectedConsents,
6126
+ consentInfo: state.consentInfo,
6127
+ locationInfo: state.locationInfo,
6128
+ model: state.model,
6129
+ overrides: state.overrides,
6130
+ scripts: state.scripts?.map((script)=>({
6131
+ id: script.id
6132
+ })),
6133
+ loadedScripts: state.loadedScripts
6134
+ };
6135
+ }
4341
6136
  function createDevTools(options = {}) {
4342
6137
  const { namespace = 'c15tStore', position = 'bottom-right', defaultOpen = false } = options;
4343
6138
  const stateManager = state_manager_createStateManager({
4344
6139
  position,
4345
6140
  isOpen: defaultOpen
4346
6141
  });
4347
- let originalCallbacks = {};
6142
+ let detachInstrumentation = null;
4348
6143
  const storeConnector = store_connector_createStoreConnector({
4349
6144
  namespace,
4350
- onConnect: (state, store)=>{
6145
+ onConnect: (_state, store)=>{
6146
+ detachInstrumentation?.();
6147
+ detachInstrumentation = store_instrumentation_registerStoreInstrumentation({
6148
+ namespace,
6149
+ store,
6150
+ onEvent: (event)=>{
6151
+ stateManager.addEvent(event);
6152
+ }
6153
+ });
4351
6154
  stateManager.setConnected(true);
4352
6155
  stateManager.addEvent({
4353
6156
  type: 'info',
4354
6157
  message: 'Connected to c15tStore'
4355
6158
  });
4356
- originalCallbacks = {
4357
- ...state.callbacks
4358
- };
4359
- store.getState().setCallback('onBannerFetched', (payload)=>{
4360
- stateManager.addEvent({
4361
- type: 'info',
4362
- message: `Banner fetched: ${String(payload.jurisdiction)}`,
4363
- data: payload
4364
- });
4365
- if ('function' == typeof originalCallbacks.onBannerFetched) originalCallbacks.onBannerFetched(payload);
4366
- });
4367
- store.getState().setCallback('onConsentSet', (payload)=>{
4368
- stateManager.addEvent({
4369
- type: 'consent_set',
4370
- message: 'Consent preferences updated',
4371
- data: payload
4372
- });
4373
- if ('function' == typeof originalCallbacks.onConsentSet) originalCallbacks.onConsentSet(payload);
4374
- });
4375
- store.getState().setCallback('onError', (payload)=>{
4376
- stateManager.addEvent({
4377
- type: 'error',
4378
- message: `Error: ${payload.error}`,
4379
- data: payload
4380
- });
4381
- if ('function' == typeof originalCallbacks.onError) originalCallbacks.onError(payload);
4382
- });
4383
- store.getState().setCallback('onBeforeConsentRevocationReload', (payload)=>{
4384
- stateManager.addEvent({
4385
- type: 'info',
4386
- message: 'Consent revocation - page will reload',
4387
- data: payload
6159
+ const persistedOverrides = override_storage_loadPersistedOverrides();
6160
+ if (persistedOverrides) {
6161
+ const currentOverrides = normalizeOverridesForPersistence(store.getState().overrides);
6162
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
6163
+ country: persistedOverrides.country,
6164
+ region: persistedOverrides.region,
6165
+ language: persistedOverrides.language,
6166
+ gpc: persistedOverrides.gpc
6167
+ }).then(()=>{
6168
+ stateManager.addEvent({
6169
+ type: 'info',
6170
+ message: 'Applied persisted devtools overrides',
6171
+ data: {
6172
+ country: persistedOverrides.country,
6173
+ region: persistedOverrides.region,
6174
+ language: persistedOverrides.language,
6175
+ gpc: persistedOverrides.gpc
6176
+ }
6177
+ });
6178
+ }).catch(()=>{
6179
+ stateManager.addEvent({
6180
+ type: 'error',
6181
+ message: 'Failed to apply persisted devtools overrides'
6182
+ });
4388
6183
  });
4389
- if ('function' == typeof originalCallbacks.onBeforeConsentRevocationReload) originalCallbacks.onBeforeConsentRevocationReload(payload);
4390
- });
6184
+ }
4391
6185
  },
4392
6186
  onDisconnect: ()=>{
4393
6187
  stateManager.setConnected(false);
6188
+ detachInstrumentation?.();
6189
+ detachInstrumentation = null;
4394
6190
  stateManager.addEvent({
4395
6191
  type: 'error',
4396
6192
  message: 'Disconnected from c15tStore'
@@ -4398,22 +6194,56 @@ function createDevTools(options = {}) {
4398
6194
  },
4399
6195
  onStateChange: ()=>{}
4400
6196
  });
6197
+ const panelRenderer = panel_renderer_createPanelRenderer({
6198
+ storeConnector,
6199
+ stateManager,
6200
+ enableEventLogging: true,
6201
+ onPersistOverrides: override_storage_persistOverrides,
6202
+ onClearPersistedOverrides: override_storage_clearPersistedOverrides,
6203
+ onCopyState: async (state)=>{
6204
+ try {
6205
+ await navigator.clipboard.writeText(JSON.stringify(createStateCopy(state), null, 2));
6206
+ return true;
6207
+ } catch {
6208
+ return false;
6209
+ }
6210
+ },
6211
+ onExportDebugBundle: ()=>{
6212
+ const bundle = debug_bundle_createDebugBundle({
6213
+ namespace,
6214
+ devToolsState: stateManager.getState(),
6215
+ connection: storeConnector.getDiagnostics(),
6216
+ recentEvents: stateManager.getState().eventLog.slice(0, 100),
6217
+ storeState: debug_bundle_sanitizeStoreState(storeConnector.getState())
6218
+ });
6219
+ debug_bundle_downloadDebugBundle(bundle);
6220
+ }
6221
+ });
4401
6222
  let tabsInstance = null;
6223
+ const panelHeightAnimator = createPanelHeightAnimator();
4402
6224
  const panelInstance = createPanel({
4403
6225
  stateManager,
4404
6226
  storeConnector,
6227
+ namespace,
4405
6228
  onRenderContent: (container)=>{
4406
- renderContent(container, stateManager, storeConnector);
6229
+ renderContent(container);
4407
6230
  }
4408
6231
  });
4409
- function renderContent(container, stateManager, storeConnector) {
6232
+ function renderContent(container) {
6233
+ const panel = container.parentElement;
6234
+ const previousPanelHeight = panel?.getBoundingClientRect().height ?? 0;
4410
6235
  clearElement(container);
4411
6236
  const storeState = storeConnector.getState();
4412
6237
  const disabledTabs = [];
4413
6238
  if (!storeState || 'iab' !== storeState.model) disabledTabs.push('iab');
6239
+ let currentActiveTab = stateManager.getState().activeTab;
6240
+ if (disabledTabs.includes(currentActiveTab)) {
6241
+ stateManager.setActiveTab('consents');
6242
+ currentActiveTab = 'consents';
6243
+ }
4414
6244
  if (tabsInstance) tabsInstance.destroy();
4415
6245
  tabsInstance = tabs_createTabs({
4416
- activeTab: stateManager.getState().activeTab,
6246
+ activeTab: currentActiveTab,
4417
6247
  onTabChange: (tab)=>{
4418
6248
  stateManager.setActiveTab(tab);
4419
6249
  },
@@ -4428,202 +6258,9 @@ function createDevTools(options = {}) {
4428
6258
  }
4429
6259
  });
4430
6260
  container.appendChild(panelContent);
4431
- const state = stateManager.getState();
4432
- const getStoreState = ()=>storeConnector.getState();
4433
- switch(state.activeTab){
4434
- case 'consents':
4435
- consents_renderConsentsPanel(panelContent, {
4436
- getState: getStoreState,
4437
- onConsentChange: (name, value)=>{
4438
- const store = storeConnector.getStore();
4439
- if (store) {
4440
- const consentName = String(name);
4441
- store.getState().setSelectedConsent(consentName, value);
4442
- stateManager.addEvent({
4443
- type: 'info',
4444
- message: `${consentName} toggled to ${value} (not saved)`,
4445
- data: {
4446
- name: consentName,
4447
- value
4448
- }
4449
- });
4450
- }
4451
- },
4452
- onSave: ()=>{
4453
- const store = storeConnector.getStore();
4454
- if (store) {
4455
- store.getState().saveConsents('custom');
4456
- stateManager.addEvent({
4457
- type: 'consent_save',
4458
- message: 'Saved consent preferences'
4459
- });
4460
- }
4461
- },
4462
- onAcceptAll: ()=>{
4463
- const store = storeConnector.getStore();
4464
- if (store) {
4465
- store.getState().saveConsents('all');
4466
- stateManager.addEvent({
4467
- type: 'consent_save',
4468
- message: 'Accepted all consents'
4469
- });
4470
- }
4471
- },
4472
- onRejectAll: ()=>{
4473
- const store = storeConnector.getStore();
4474
- if (store) {
4475
- store.getState().saveConsents('necessary');
4476
- stateManager.addEvent({
4477
- type: 'consent_save',
4478
- message: 'Rejected all optional consents'
4479
- });
4480
- }
4481
- },
4482
- onReset: async ()=>{
4483
- const store = storeConnector.getStore();
4484
- if (store) await reset_consents_resetAllConsents(store, stateManager);
4485
- }
4486
- });
4487
- break;
4488
- case 'location':
4489
- location_renderLocationPanel(panelContent, {
4490
- getState: getStoreState,
4491
- onSetOverrides: async (overrides)=>{
4492
- const store = storeConnector.getStore();
4493
- if (store) {
4494
- const currentOverrides = store.getState().overrides || {};
4495
- await store.getState().setOverrides({
4496
- ...currentOverrides,
4497
- ...overrides
4498
- });
4499
- stateManager.addEvent({
4500
- type: 'info',
4501
- message: 'Overrides updated',
4502
- data: overrides
4503
- });
4504
- await store.getState().initConsentManager();
4505
- stateManager.addEvent({
4506
- type: 'info',
4507
- message: 'Consent manager re-initialized with new overrides'
4508
- });
4509
- }
4510
- },
4511
- onClearOverrides: async ()=>{
4512
- const store = storeConnector.getStore();
4513
- if (store) {
4514
- await store.getState().setOverrides(void 0);
4515
- stateManager.addEvent({
4516
- type: 'info',
4517
- message: 'Overrides cleared'
4518
- });
4519
- await store.getState().initConsentManager();
4520
- stateManager.addEvent({
4521
- type: 'info',
4522
- message: 'Consent manager re-initialized'
4523
- });
4524
- }
4525
- }
4526
- });
4527
- break;
4528
- case "scripts":
4529
- scripts_renderScriptsPanel(panelContent, {
4530
- getState: getStoreState
4531
- });
4532
- break;
4533
- case 'iab':
4534
- iab_renderIabPanel(panelContent, {
4535
- getState: getStoreState,
4536
- onReset: async ()=>{
4537
- const store = storeConnector.getStore();
4538
- if (store) await reset_consents_resetAllConsents(store, stateManager);
4539
- }
4540
- });
4541
- break;
4542
- case 'events':
4543
- events_renderEventsPanel(panelContent, {
4544
- getEvents: ()=>stateManager.getState().eventLog,
4545
- onClear: ()=>{
4546
- stateManager.clearEventLog();
4547
- stateManager.addEvent({
4548
- type: 'info',
4549
- message: 'Event log cleared'
4550
- });
4551
- }
4552
- });
4553
- break;
4554
- case 'actions':
4555
- actions_renderActionsPanel(panelContent, {
4556
- getState: getStoreState,
4557
- onResetConsents: async ()=>{
4558
- const store = storeConnector.getStore();
4559
- if (store) await reset_consents_resetAllConsents(store, stateManager);
4560
- },
4561
- onRefetchBanner: async ()=>{
4562
- const store = storeConnector.getStore();
4563
- if (store) {
4564
- await store.getState().initConsentManager();
4565
- stateManager.addEvent({
4566
- type: 'info',
4567
- message: 'Banner data refetched'
4568
- });
4569
- }
4570
- },
4571
- onShowBanner: ()=>{
4572
- const store = storeConnector.getStore();
4573
- if (store) {
4574
- store.getState().setActiveUI('banner', {
4575
- force: true
4576
- });
4577
- stateManager.addEvent({
4578
- type: 'info',
4579
- message: 'Banner shown'
4580
- });
4581
- }
4582
- },
4583
- onOpenPreferences: ()=>{
4584
- const store = storeConnector.getStore();
4585
- if (store) {
4586
- store.getState().setActiveUI('dialog');
4587
- stateManager.addEvent({
4588
- type: 'info',
4589
- message: 'Preference center opened'
4590
- });
4591
- }
4592
- },
4593
- onCopyState: ()=>{
4594
- const state = storeConnector.getState();
4595
- if (state) {
4596
- const stateCopy = {
4597
- consents: state.consents,
4598
- consentInfo: state.consentInfo,
4599
- locationInfo: state.locationInfo,
4600
- model: state.model,
4601
- overrides: state.overrides,
4602
- scripts: state.scripts?.map((s)=>({
4603
- id: s.id
4604
- })),
4605
- loadedScripts: state.loadedScripts
4606
- };
4607
- navigator.clipboard.writeText(JSON.stringify(stateCopy, null, 2)).then(()=>{
4608
- stateManager.addEvent({
4609
- type: 'info',
4610
- message: 'State copied to clipboard'
4611
- });
4612
- }).catch(()=>{
4613
- stateManager.addEvent({
4614
- type: 'error',
4615
- message: 'Failed to copy state'
4616
- });
4617
- });
4618
- }
4619
- }
4620
- });
4621
- break;
4622
- }
6261
+ panelRenderer.renderPanel(panelContent, currentActiveTab);
6262
+ if (panel) panelHeightAnimator.animate(panel, previousPanelHeight);
4623
6263
  }
4624
- storeConnector.subscribe(()=>{
4625
- panelInstance.update();
4626
- });
4627
6264
  const instance = {
4628
6265
  open: ()=>stateManager.setOpen(true),
4629
6266
  close: ()=>stateManager.setOpen(false),
@@ -4637,6 +6274,9 @@ function createDevTools(options = {}) {
4637
6274
  };
4638
6275
  },
4639
6276
  destroy: ()=>{
6277
+ detachInstrumentation?.();
6278
+ detachInstrumentation = null;
6279
+ panelHeightAnimator.destroy();
4640
6280
  tabsInstance?.destroy();
4641
6281
  panelInstance.destroy();
4642
6282
  storeConnector.destroy();