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