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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/__tests__/core/override-storage.test.d.ts +2 -0
  3. package/dist/__tests__/core/override-storage.test.d.ts.map +1 -0
  4. package/dist/__tests__/core/store-connector.test.d.ts +2 -0
  5. package/dist/__tests__/core/store-connector.test.d.ts.map +1 -0
  6. package/dist/__tests__/panels/events.test.d.ts +2 -0
  7. package/dist/__tests__/panels/events.test.d.ts.map +1 -0
  8. package/dist/__tests__/panels/iab.test.d.ts +2 -0
  9. package/dist/__tests__/panels/iab.test.d.ts.map +1 -0
  10. package/dist/__tests__/panels/scripts.test.d.ts +2 -0
  11. package/dist/__tests__/panels/scripts.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/preference-trigger.test.d.ts +2 -0
  13. package/dist/__tests__/utils/preference-trigger.test.d.ts.map +1 -0
  14. package/dist/components/panel.d.ts +1 -0
  15. package/dist/components/panel.d.ts.map +1 -1
  16. package/dist/core/devtools.d.ts.map +1 -1
  17. package/dist/core/override-storage.d.ts +7 -0
  18. package/dist/core/override-storage.d.ts.map +1 -0
  19. package/dist/core/panel-renderer.d.ts.map +1 -1
  20. package/dist/core/state-manager.d.ts +1 -1
  21. package/dist/core/state-manager.d.ts.map +1 -1
  22. package/dist/core/store-connector.d.ts +4 -0
  23. package/dist/core/store-connector.d.ts.map +1 -1
  24. package/dist/index.cjs +1092 -244
  25. package/dist/index.js +1092 -244
  26. package/dist/panels/dom-scanner.d.ts.map +1 -1
  27. package/dist/panels/events.d.ts.map +1 -1
  28. package/dist/panels/iab.d.ts +6 -0
  29. package/dist/panels/iab.d.ts.map +1 -1
  30. package/dist/panels/location.d.ts +9 -6
  31. package/dist/panels/location.d.ts.map +1 -1
  32. package/dist/panels/scripts.d.ts +2 -0
  33. package/dist/panels/scripts.d.ts.map +1 -1
  34. package/dist/react.cjs +1015 -237
  35. package/dist/react.js +1015 -237
  36. package/dist/tanstack.cjs +810 -201
  37. package/dist/tanstack.js +810 -201
  38. package/dist/utils/preference-trigger.d.ts +2 -2
  39. package/dist/utils/preference-trigger.d.ts.map +1 -1
  40. package/dist/version.d.ts +2 -0
  41. package/dist/version.d.ts.map +1 -0
  42. package/package.json +10 -8
package/dist/tanstack.js CHANGED
@@ -344,22 +344,26 @@ var __webpack_modules__ = {
344
344
  justify-content: center;
345
345
  align-items: center;
346
346
  gap: var(--c15t-space-xs, .25rem);
347
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
348
347
  border: 1px solid var(--c15t-border, #e3e3e3);
349
348
  border-radius: var(--c15t-radius-md, .5rem);
350
349
  background-color: var(--c15t-surface, #fff);
350
+ min-height: 30px;
351
351
  color: var(--c15t-text, #171717);
352
352
  font-family: inherit;
353
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
353
+ font-size: 12px;
354
354
  font-weight: var(--c15t-font-weight-medium, 500);
355
355
  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));
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)), 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));
357
+ padding: 5px 10px;
358
+ line-height: 1;
357
359
  display: inline-flex;
360
+ box-shadow: 0 1px 1px #0000000a;
358
361
  }
359
362
 
360
363
  .btn-evRVlh:hover {
361
364
  background-color: var(--c15t-surface-hover, #f7f7f7);
362
365
  border-color: var(--c15t-border-hover, #c9c9c9);
366
+ box-shadow: 0 2px 6px #00000014;
363
367
  }
364
368
 
365
369
  .btn-evRVlh:focus-visible {
@@ -367,9 +371,14 @@ var __webpack_modules__ = {
367
371
  outline-offset: 1px;
368
372
  }
369
373
 
374
+ .btn-evRVlh:active {
375
+ box-shadow: 0 1px 2px #00000014;
376
+ }
377
+
370
378
  .btn-evRVlh:disabled {
371
379
  opacity: .5;
372
380
  cursor: not-allowed;
381
+ box-shadow: none;
373
382
  }
374
383
 
375
384
  .btnPrimary-dA6nqY {
@@ -395,8 +404,10 @@ var __webpack_modules__ = {
395
404
  }
396
405
 
397
406
  .btnSmall-TjXoqZ {
398
- padding: 2px var(--c15t-space-xs, .25rem);
399
- font-size: 10px;
407
+ border-radius: var(--c15t-radius-sm, .375rem);
408
+ min-height: 26px;
409
+ padding: 3px 8px;
410
+ font-size: 11px;
400
411
  }
401
412
 
402
413
  .btnIcon-fiYQAh {
@@ -548,6 +559,50 @@ var __webpack_modules__ = {
548
559
  letter-spacing: .5px;
549
560
  }
550
561
 
562
+ .overrideField-keNdpJ {
563
+ flex-direction: column;
564
+ gap: 3px;
565
+ margin-bottom: 0;
566
+ display: flex;
567
+ }
568
+
569
+ .overrideLabel-ApMoTw {
570
+ color: var(--c15t-text-muted, #737373);
571
+ font-size: 11px;
572
+ font-weight: 600;
573
+ }
574
+
575
+ .overrideHint-yCfwGt {
576
+ color: var(--c15t-devtools-text-muted, #737373);
577
+ margin-top: 6px;
578
+ font-size: 11px;
579
+ }
580
+
581
+ .overrideActions-imdcn7 {
582
+ border-top: 1px dashed var(--c15t-border, #e3e3e3);
583
+ justify-content: space-between;
584
+ align-items: center;
585
+ gap: 8px;
586
+ margin-top: 8px;
587
+ padding-top: 8px;
588
+ display: flex;
589
+ }
590
+
591
+ .overrideActionButtons-gYOx1e {
592
+ flex-wrap: wrap;
593
+ gap: 6px;
594
+ display: flex;
595
+ }
596
+
597
+ .overrideStatus-sty_qS {
598
+ color: var(--c15t-text-muted, #737373);
599
+ font-size: 11px;
600
+ }
601
+
602
+ .overrideStatusDirty-OUdDMw {
603
+ color: var(--c15t-devtools-badge-warning, #f59f0a);
604
+ }
605
+
551
606
  .infoRow-RlB_0h {
552
607
  padding: var(--c15t-space-xs, .25rem) 0;
553
608
  justify-content: space-between;
@@ -640,6 +695,13 @@ var __webpack_modules__ = {
640
695
  section: "section-a197cB",
641
696
  sectionHeader: "sectionHeader-Xcljcw",
642
697
  sectionTitle: "sectionTitle-RUiFld",
698
+ overrideField: "overrideField-keNdpJ",
699
+ overrideLabel: "overrideLabel-ApMoTw",
700
+ overrideHint: "overrideHint-yCfwGt",
701
+ overrideActions: "overrideActions-imdcn7",
702
+ overrideActionButtons: "overrideActionButtons-gYOx1e",
703
+ overrideStatus: "overrideStatus-sty_qS",
704
+ overrideStatusDirty: "overrideStatusDirty-OUdDMw",
643
705
  infoRow: "infoRow-RlB_0h",
644
706
  infoLabel: "infoLabel-_pbK33",
645
707
  infoValue: "infoValue-flMl_e",
@@ -1122,16 +1184,23 @@ var __webpack_modules__ = {
1122
1184
  ___CSS_LOADER_EXPORT___.push([
1123
1185
  module.id,
1124
1186
  `.tabList-IyuiBE {
1187
+ align-items: center;
1125
1188
  gap: var(--c15t-space-xs, .25rem);
1126
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
1189
+ padding: var(--c15t-space-sm, .5rem) var(--c15t-space-sm, .5rem);
1127
1190
  border-bottom: 1px solid var(--c15t-border, #e3e3e3);
1128
1191
  background-color: var(--c15t-surface, #fff);
1129
1192
  scrollbar-width: none;
1130
1193
  -ms-overflow-style: none;
1194
+ scroll-padding-inline-end: var(--c15t-space-sm, .5rem);
1131
1195
  display: flex;
1132
1196
  overflow-x: auto;
1133
1197
  }
1134
1198
 
1199
+ .tabList-IyuiBE:after {
1200
+ content: "";
1201
+ flex: 0 0 var(--c15t-space-sm, .5rem);
1202
+ }
1203
+
1135
1204
  .tabList-IyuiBE::-webkit-scrollbar {
1136
1205
  display: none;
1137
1206
  }
@@ -1139,17 +1208,17 @@ var __webpack_modules__ = {
1139
1208
  .tab-yfDEqg {
1140
1209
  align-items: center;
1141
1210
  gap: var(--c15t-space-xs, .25rem);
1142
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
1143
1211
  border-radius: var(--c15t-radius-md, .5rem);
1144
1212
  color: var(--c15t-text-muted, #737373);
1145
1213
  font-family: inherit;
1146
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
1214
+ font-size: 11px;
1147
1215
  font-weight: var(--c15t-font-weight-medium, 500);
1148
1216
  cursor: pointer;
1149
1217
  white-space: nowrap;
1150
1218
  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));
1151
1219
  background-color: #0000;
1152
1220
  border: none;
1221
+ padding: 3px 7px;
1153
1222
  display: flex;
1154
1223
  }
1155
1224
 
@@ -2284,13 +2353,13 @@ function consents_renderConsentsPanel(container, options) {
2284
2353
  },
2285
2354
  children: [
2286
2355
  createButton({
2287
- text: 'All',
2356
+ text: 'Accept',
2288
2357
  variant: 'primary',
2289
2358
  small: true,
2290
2359
  onClick: onAcceptAll
2291
2360
  }),
2292
2361
  createButton({
2293
- text: 'None',
2362
+ text: 'Reject',
2294
2363
  variant: 'default',
2295
2364
  small: true,
2296
2365
  onClick: onRejectAll
@@ -2338,16 +2407,22 @@ function consents_renderConsentsPanel(container, options) {
2338
2407
  function formatConsentName(name) {
2339
2408
  return name.replace(/_/g, ' ').replace(/\b\w/g, (l)=>l.toUpperCase());
2340
2409
  }
2410
+ let activeFilter = 'all';
2411
+ let selectedEventId = null;
2341
2412
  function events_renderEventsPanel(container, options) {
2342
2413
  const { getEvents, onClear } = options;
2343
2414
  renderer_clearElement(container);
2344
- const events = getEvents();
2415
+ const allEvents = getEvents();
2416
+ const events = allEvents.filter((event)=>matchesFilter(event, activeFilter));
2417
+ if (!events.some((event)=>event.id === selectedEventId)) selectedEventId = events[0]?.id ?? null;
2418
+ const selectedEvent = events.find((event)=>event.id === selectedEventId) ?? null;
2345
2419
  const header = renderer_div({
2346
2420
  style: {
2347
2421
  display: 'flex',
2348
2422
  alignItems: 'center',
2349
2423
  justifyContent: 'space-between',
2350
- padding: '12px 16px 8px'
2424
+ padding: '12px 16px 8px',
2425
+ gap: '8px'
2351
2426
  },
2352
2427
  children: [
2353
2428
  renderer_span({
@@ -2358,44 +2433,130 @@ function events_renderEventsPanel(container, options) {
2358
2433
  textTransform: 'uppercase',
2359
2434
  letterSpacing: '0.5px'
2360
2435
  },
2361
- text: `Events (${events.length})`
2436
+ text: `Events (${events.length}/${allEvents.length})`
2362
2437
  }),
2363
- createButton({
2364
- text: 'Clear',
2365
- small: true,
2366
- onClick: onClear
2438
+ renderer_div({
2439
+ style: {
2440
+ display: 'flex',
2441
+ gap: '6px'
2442
+ },
2443
+ children: [
2444
+ createButton({
2445
+ text: 'Export',
2446
+ small: true,
2447
+ onClick: ()=>exportEvents(allEvents)
2448
+ }),
2449
+ createButton({
2450
+ text: 'Clear',
2451
+ small: true,
2452
+ onClick: ()=>{
2453
+ onClear();
2454
+ selectedEventId = null;
2455
+ events_renderEventsPanel(container, options);
2456
+ }
2457
+ })
2458
+ ]
2367
2459
  })
2368
2460
  ]
2369
2461
  });
2370
2462
  container.appendChild(header);
2463
+ container.appendChild(renderer_div({
2464
+ style: {
2465
+ display: 'flex',
2466
+ flexWrap: 'wrap',
2467
+ gap: '6px',
2468
+ padding: '0 16px 8px'
2469
+ },
2470
+ children: EVENT_FILTERS.map((filter)=>createFilterButton(filter, filter === activeFilter, ()=>{
2471
+ activeFilter = filter;
2472
+ selectedEventId = null;
2473
+ events_renderEventsPanel(container, options);
2474
+ }))
2475
+ }));
2371
2476
  const eventList = renderer_div({
2372
2477
  style: {
2373
2478
  display: 'flex',
2374
2479
  flexDirection: 'column',
2375
2480
  gap: '4px',
2376
2481
  padding: '0 12px 12px',
2377
- maxHeight: '400px',
2482
+ maxHeight: '300px',
2378
2483
  overflowY: 'auto'
2379
2484
  }
2380
2485
  });
2381
- if (0 === events.length) {
2382
- const emptyState = renderer_div({
2383
- style: {
2384
- padding: '32px 16px',
2385
- textAlign: 'center',
2386
- color: 'var(--c15t-text-muted)',
2387
- fontSize: 'var(--c15t-devtools-font-size-sm)'
2388
- },
2389
- text: 'No events recorded yet'
2390
- });
2391
- eventList.appendChild(emptyState);
2392
- } else for (const event of events){
2393
- const eventItem = createEventItem(event);
2394
- eventList.appendChild(eventItem);
2395
- }
2486
+ if (0 === events.length) eventList.appendChild(renderer_div({
2487
+ style: {
2488
+ padding: '20px 16px',
2489
+ textAlign: 'center',
2490
+ color: 'var(--c15t-text-muted)',
2491
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
2492
+ },
2493
+ text: 'No events match this filter'
2494
+ }));
2495
+ else for (const event of events)eventList.appendChild(createEventItem(event, event.id === selectedEventId, ()=>{
2496
+ selectedEventId = event.id;
2497
+ events_renderEventsPanel(container, options);
2498
+ }));
2396
2499
  container.appendChild(eventList);
2500
+ container.appendChild(createPayloadSection(selectedEvent));
2501
+ }
2502
+ const EVENT_FILTERS = [
2503
+ 'all',
2504
+ 'error',
2505
+ 'consent',
2506
+ 'network',
2507
+ 'iab'
2508
+ ];
2509
+ function createFilterButton(filter, active, onClick) {
2510
+ return createButton({
2511
+ text: filter.toUpperCase(),
2512
+ small: true,
2513
+ variant: active ? 'primary' : 'default',
2514
+ onClick
2515
+ });
2516
+ }
2517
+ function matchesFilter(event, filter) {
2518
+ if ('all' === filter) return true;
2519
+ if ('error' === filter) return 'error' === event.type;
2520
+ if ('consent' === filter) return 'consent_set' === event.type || 'consent_save' === event.type || 'consent_reset' === event.type;
2521
+ if ('network' === filter) return 'network' === event.type;
2522
+ return 'iab' === event.type;
2397
2523
  }
2398
- function createEventItem(event) {
2524
+ function createPayloadSection(event) {
2525
+ const payload = event?.data ? JSON.stringify(event.data, null, 2) : null;
2526
+ return renderer_div({
2527
+ style: {
2528
+ padding: '0 12px 12px'
2529
+ },
2530
+ children: [
2531
+ renderer_div({
2532
+ style: {
2533
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
2534
+ fontWeight: '600',
2535
+ color: 'var(--c15t-text-muted)',
2536
+ textTransform: 'uppercase',
2537
+ letterSpacing: '0.5px',
2538
+ marginBottom: '6px'
2539
+ },
2540
+ text: 'Payload'
2541
+ }),
2542
+ renderer_div({
2543
+ className: styles_components_module.gridCard ?? '',
2544
+ style: {
2545
+ padding: '8px',
2546
+ fontFamily: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
2547
+ fontSize: '11px',
2548
+ color: 'var(--c15t-text-muted)',
2549
+ maxHeight: '140px',
2550
+ overflowY: 'auto',
2551
+ whiteSpace: 'pre-wrap',
2552
+ wordBreak: 'break-word'
2553
+ },
2554
+ text: payload || 'Select an event with payload data'
2555
+ })
2556
+ ]
2557
+ });
2558
+ }
2559
+ function createEventItem(event, selected, onSelect) {
2399
2560
  const time = formatTime(event.timestamp);
2400
2561
  const icon = getEventIcon(event.type);
2401
2562
  const color = getEventColor(event.type);
@@ -2406,8 +2567,11 @@ function createEventItem(event) {
2406
2567
  alignItems: 'center',
2407
2568
  gap: '8px',
2408
2569
  padding: '6px 10px',
2409
- fontSize: 'var(--c15t-devtools-font-size-xs)'
2570
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
2571
+ cursor: 'pointer',
2572
+ borderColor: selected ? 'var(--c15t-devtools-badge-info, #3b82f6)' : 'var(--c15t-border)'
2410
2573
  },
2574
+ onClick: onSelect,
2411
2575
  children: [
2412
2576
  renderer_span({
2413
2577
  style: {
@@ -2436,6 +2600,21 @@ function createEventItem(event) {
2436
2600
  ]
2437
2601
  });
2438
2602
  }
2603
+ function exportEvents(events) {
2604
+ const json = JSON.stringify(events, null, 2);
2605
+ const blob = new Blob([
2606
+ json
2607
+ ], {
2608
+ type: 'application/json'
2609
+ });
2610
+ const url = URL.createObjectURL(blob);
2611
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2612
+ const a = document.createElement('a');
2613
+ a.href = url;
2614
+ a.download = `c15t-events-${timestamp}.json`;
2615
+ a.click();
2616
+ URL.revokeObjectURL(url);
2617
+ }
2439
2618
  function formatTime(timestamp) {
2440
2619
  const date = new Date(timestamp);
2441
2620
  return date.toLocaleTimeString('en-US', {
@@ -2454,7 +2633,10 @@ function getEventIcon(type) {
2454
2633
  return '○';
2455
2634
  case 'error':
2456
2635
  return '✕';
2457
- case 'info':
2636
+ case 'network':
2637
+ return '◉';
2638
+ case 'iab':
2639
+ return '◆';
2458
2640
  default:
2459
2641
  return '○';
2460
2642
  }
@@ -2468,13 +2650,16 @@ function getEventColor(type) {
2468
2650
  return 'var(--c15t-devtools-badge-warning, #f59e0b)';
2469
2651
  case 'error':
2470
2652
  return 'var(--c15t-devtools-badge-error, #ef4444)';
2471
- case 'info':
2653
+ case 'network':
2654
+ return 'var(--c15t-devtools-badge-warning, #f59e0b)';
2655
+ case 'iab':
2656
+ return 'var(--c15t-devtools-badge-info, #3b82f6)';
2472
2657
  default:
2473
2658
  return 'var(--c15t-text-muted)';
2474
2659
  }
2475
2660
  }
2476
2661
  function iab_renderIabPanel(container, options) {
2477
- const { getState, onReset } = options;
2662
+ const { getState, onSetPurposeConsent, onSetVendorConsent, onSetSpecialFeatureOptIn, onAcceptAll, onRejectAll, onSave, onReset } = options;
2478
2663
  renderer_clearElement(container);
2479
2664
  const state = getState();
2480
2665
  if (!state) return void container.appendChild(renderer_div({
@@ -2552,7 +2737,9 @@ function iab_renderIabPanel(container, options) {
2552
2737
  for (const [purposeId, consent] of purposeEntries){
2553
2738
  const purposeInfo = purposes[purposeId];
2554
2739
  const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
2555
- purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent)));
2740
+ purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent), (value)=>{
2741
+ onSetPurposeConsent(Number(purposeId), value);
2742
+ }));
2556
2743
  }
2557
2744
  const purposesSection = createSection({
2558
2745
  title: `Purposes (${purposeEntries.length})`,
@@ -2578,7 +2765,9 @@ function iab_renderIabPanel(container, options) {
2578
2765
  for (const [featureId, optIn] of specialFeatureEntries){
2579
2766
  const featureInfo = specialFeatures[featureId];
2580
2767
  const featureName = featureInfo?.name || `Special Feature ${featureId}`;
2581
- specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn)));
2768
+ specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn), (value)=>{
2769
+ onSetSpecialFeatureOptIn(Number(featureId), value);
2770
+ }, 'feature'));
2582
2771
  }
2583
2772
  const specialFeaturesSection = createSection({
2584
2773
  title: `Special Features (${specialFeatureEntries.length})`,
@@ -2618,7 +2807,9 @@ function iab_renderIabPanel(container, options) {
2618
2807
  overflowY: 'auto'
2619
2808
  }
2620
2809
  });
2621
- for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab'));
2810
+ for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab', (value)=>{
2811
+ onSetVendorConsent(Number(vendorId), value);
2812
+ }));
2622
2813
  const vendorsSection = createSection({
2623
2814
  title: `IAB Vendors (${iabVendors.length})`,
2624
2815
  children: [
@@ -2637,7 +2828,9 @@ function iab_renderIabPanel(container, options) {
2637
2828
  overflowY: 'auto'
2638
2829
  }
2639
2830
  });
2640
- for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom'));
2831
+ for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom', (value)=>{
2832
+ onSetVendorConsent(vendorId, value);
2833
+ }));
2641
2834
  const customVendorsSection = createSection({
2642
2835
  title: `Custom Vendors (${customVendors.length})`,
2643
2836
  children: [
@@ -2659,15 +2852,40 @@ function iab_renderIabPanel(container, options) {
2659
2852
  style: {
2660
2853
  display: 'flex',
2661
2854
  alignItems: 'center',
2662
- justifyContent: 'flex-end',
2855
+ justifyContent: 'space-between',
2663
2856
  padding: '12px 16px',
2664
2857
  marginTop: 'auto',
2665
2858
  borderTop: '1px solid var(--c15t-border)',
2666
2859
  backgroundColor: 'var(--c15t-surface)'
2667
2860
  },
2668
2861
  children: [
2862
+ renderer_div({
2863
+ style: {
2864
+ display: 'flex',
2865
+ gap: '6px'
2866
+ },
2867
+ children: [
2868
+ createButton({
2869
+ text: 'Accept All',
2870
+ variant: 'primary',
2871
+ small: true,
2872
+ onClick: onAcceptAll
2873
+ }),
2874
+ createButton({
2875
+ text: 'Reject All',
2876
+ small: true,
2877
+ onClick: onRejectAll
2878
+ }),
2879
+ createButton({
2880
+ text: 'Save',
2881
+ variant: 'primary',
2882
+ small: true,
2883
+ onClick: onSave
2884
+ })
2885
+ ]
2886
+ }),
2669
2887
  createButton({
2670
- text: 'Reset All',
2888
+ text: 'Reset',
2671
2889
  variant: 'danger',
2672
2890
  small: true,
2673
2891
  onClick: onReset
@@ -2676,7 +2894,7 @@ function iab_renderIabPanel(container, options) {
2676
2894
  });
2677
2895
  container.appendChild(footer);
2678
2896
  }
2679
- function createPurposeRow(id, name, consent) {
2897
+ function createPurposeRow(id, name, consent, onChange, ariaKind = 'purpose') {
2680
2898
  return renderer_div({
2681
2899
  style: {
2682
2900
  display: 'flex',
@@ -2699,14 +2917,28 @@ function createPurposeRow(id, name, consent) {
2699
2917
  text: `${id}. ${name}`,
2700
2918
  title: name
2701
2919
  }),
2702
- createBadge({
2703
- text: consent ? '✓' : '✕',
2704
- variant: consent ? 'success' : 'error'
2920
+ renderer_div({
2921
+ style: {
2922
+ display: 'flex',
2923
+ alignItems: 'center',
2924
+ gap: '6px'
2925
+ },
2926
+ children: [
2927
+ createBadge({
2928
+ text: consent ? '✓' : '✕',
2929
+ variant: consent ? 'success' : 'error'
2930
+ }),
2931
+ createToggle({
2932
+ checked: consent,
2933
+ onChange,
2934
+ ariaLabel: `Toggle ${ariaKind} ${id}`
2935
+ })
2936
+ ]
2705
2937
  })
2706
2938
  ]
2707
2939
  });
2708
2940
  }
2709
- function createVendorRow(id, name, consent, type) {
2941
+ function createVendorRow(id, name, consent, type, onChange) {
2710
2942
  return renderer_div({
2711
2943
  style: {
2712
2944
  display: 'flex',
@@ -2753,16 +2985,21 @@ function createVendorRow(id, name, consent, type) {
2753
2985
  createBadge({
2754
2986
  text: consent ? '✓' : '✕',
2755
2987
  variant: consent ? 'success' : 'error'
2988
+ }),
2989
+ createToggle({
2990
+ checked: consent,
2991
+ onChange,
2992
+ ariaLabel: `Toggle vendor ${id}`
2756
2993
  })
2757
2994
  ]
2758
2995
  });
2759
2996
  }
2760
2997
  function truncateText(text, maxLength) {
2761
2998
  if (text.length <= maxLength) return text;
2762
- return text.slice(0, maxLength - 3) + '...';
2999
+ return `${text.slice(0, maxLength - 3)}...`;
2763
3000
  }
2764
3001
  function location_renderLocationPanel(container, options) {
2765
- const { getState, onSetOverrides, onClearOverrides } = options;
3002
+ const { getState, onApplyOverrides, onClearOverrides } = options;
2766
3003
  renderer_clearElement(container);
2767
3004
  const state = getState();
2768
3005
  if (!state) return void container.appendChild(renderer_div({
@@ -2783,145 +3020,230 @@ function location_renderLocationPanel(container, options) {
2783
3020
  createCompactInfoCard('Jurisdiction', locationInfo?.jurisdiction || '—'),
2784
3021
  createCompactInfoCard('Language', translationConfig?.defaultLanguage || '—')
2785
3022
  ];
3023
+ gridItems.push(createCompactInfoCard('GPC', getEffectiveGpcLabel(overrides?.gpc)));
2786
3024
  if (state.model) gridItems.push(createCompactInfoCard('Model', getModelLabel(state.model)));
2787
3025
  const locationGrid = createGrid({
2788
- columns: 2,
3026
+ columns: 3,
2789
3027
  children: gridItems
2790
3028
  });
2791
3029
  container.appendChild(locationGrid);
3030
+ const initialDraft = getDraftFromOverrides(overrides);
3031
+ let appliedOverrides = normalizeOverrideDraft(initialDraft);
3032
+ let isSubmitting = false;
3033
+ const countryField = createOverrideSelect({
3034
+ label: 'Country',
3035
+ selectOptions: COUNTRY_OPTIONS,
3036
+ value: initialDraft.country
3037
+ });
3038
+ const regionField = createOverrideInput({
3039
+ label: 'Region',
3040
+ placeholder: 'e.g., CA, NY, BE',
3041
+ value: initialDraft.region
3042
+ });
3043
+ const languageField = createOverrideInput({
3044
+ label: 'Language',
3045
+ placeholder: 'e.g., de, fr, en-US',
3046
+ value: initialDraft.language
3047
+ });
3048
+ const gpcField = createOverrideSelect({
3049
+ label: 'GPC',
3050
+ selectOptions: GPC_OPTIONS,
3051
+ value: initialDraft.gpc
3052
+ });
3053
+ const formStatus = renderer_span({
3054
+ className: styles_components_module.overrideStatus,
3055
+ text: 'In sync'
3056
+ });
3057
+ const applyButton = createButton({
3058
+ text: 'Apply',
3059
+ variant: 'primary',
3060
+ small: true,
3061
+ disabled: true,
3062
+ onClick: ()=>{
3063
+ applyDraft();
3064
+ }
3065
+ });
3066
+ const revertButton = createButton({
3067
+ text: 'Revert',
3068
+ small: true,
3069
+ disabled: true,
3070
+ onClick: ()=>{
3071
+ setDraftValues(getDraftFromOverrides(appliedOverrides));
3072
+ updateFormState();
3073
+ }
3074
+ });
3075
+ const clearButton = createButton({
3076
+ text: 'Clear',
3077
+ small: true,
3078
+ onClick: ()=>{
3079
+ clearDraftAndOverrides();
3080
+ }
3081
+ });
3082
+ const overrideFieldsGrid = renderer_div({
3083
+ style: {
3084
+ display: 'grid',
3085
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
3086
+ gap: '8px 10px'
3087
+ },
3088
+ children: [
3089
+ countryField.element,
3090
+ regionField.element,
3091
+ languageField.element,
3092
+ gpcField.element
3093
+ ]
3094
+ });
2792
3095
  const overrideSection = createSection({
2793
3096
  title: 'Override Settings',
2794
- actions: [
2795
- createButton({
2796
- text: 'Clear',
2797
- small: true,
2798
- onClick: onClearOverrides
2799
- })
2800
- ],
2801
3097
  children: [
2802
- createOverrideSelect({
2803
- label: 'Country',
2804
- selectOptions: COUNTRY_OPTIONS,
2805
- value: overrides?.country || '',
2806
- onChange: (value)=>onSetOverrides({
2807
- country: value || void 0
2808
- })
2809
- }),
2810
- createOverrideInput({
2811
- label: 'Region',
2812
- placeholder: 'e.g., CA, NY, BE',
2813
- value: overrides?.region || '',
2814
- onChange: (value)=>onSetOverrides({
2815
- region: value || void 0
2816
- })
3098
+ overrideFieldsGrid,
3099
+ renderer_span({
3100
+ className: styles_components_module.overrideHint,
3101
+ text: 'GPC override only affects opt-out or unregulated jurisdictions.'
2817
3102
  }),
2818
- createOverrideInput({
2819
- label: 'Language',
2820
- placeholder: 'e.g., de, fr, en',
2821
- value: overrides?.language || '',
2822
- onChange: (value)=>onSetOverrides({
2823
- language: value || void 0
2824
- })
3103
+ renderer_div({
3104
+ className: styles_components_module.overrideActions,
3105
+ children: [
3106
+ renderer_div({
3107
+ className: styles_components_module.overrideActionButtons,
3108
+ children: [
3109
+ revertButton,
3110
+ applyButton,
3111
+ clearButton
3112
+ ]
3113
+ }),
3114
+ formStatus
3115
+ ]
2825
3116
  })
2826
3117
  ]
2827
3118
  });
2828
3119
  container.appendChild(overrideSection);
2829
- const hasOverrides = overrides && (overrides.country || overrides.region || overrides.language);
2830
- if (hasOverrides) {
2831
- const overrideBanner = renderer_div({
2832
- style: {
2833
- padding: '8px 16px',
2834
- backgroundColor: 'var(--c15t-devtools-badge-info-bg)',
2835
- color: 'var(--c15t-devtools-badge-info)',
2836
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2837
- borderTop: '1px solid var(--c15t-devtools-border)'
2838
- },
2839
- text: 'Overrides are active. This may affect consent behavior.'
3120
+ countryField.control.addEventListener('change', updateFormState);
3121
+ regionField.control.addEventListener('input', updateFormState);
3122
+ languageField.control.addEventListener('input', updateFormState);
3123
+ gpcField.control.addEventListener('change', updateFormState);
3124
+ updateFormState();
3125
+ async function applyDraft() {
3126
+ if (isSubmitting) return;
3127
+ const draftOverrides = getDraftOverrides();
3128
+ if (overridesEqual(draftOverrides, appliedOverrides)) return;
3129
+ isSubmitting = true;
3130
+ updateFormState();
3131
+ try {
3132
+ await onApplyOverrides(draftOverrides);
3133
+ appliedOverrides = draftOverrides;
3134
+ } finally{
3135
+ isSubmitting = false;
3136
+ updateFormState();
3137
+ }
3138
+ }
3139
+ async function clearDraftAndOverrides() {
3140
+ if (isSubmitting) return;
3141
+ isSubmitting = true;
3142
+ updateFormState();
3143
+ try {
3144
+ await onClearOverrides();
3145
+ appliedOverrides = {};
3146
+ setDraftValues(getDraftFromOverrides(void 0));
3147
+ } finally{
3148
+ isSubmitting = false;
3149
+ updateFormState();
3150
+ }
3151
+ }
3152
+ function getDraftOverrides() {
3153
+ return normalizeOverrideDraft({
3154
+ country: countryField.control.value,
3155
+ region: regionField.control.value,
3156
+ language: languageField.control.value,
3157
+ gpc: gpcField.control.value
2840
3158
  });
2841
- container.appendChild(overrideBanner);
3159
+ }
3160
+ function setDraftValues(draft) {
3161
+ countryField.control.value = draft.country;
3162
+ regionField.control.value = draft.region;
3163
+ languageField.control.value = draft.language;
3164
+ gpcField.control.value = draft.gpc;
3165
+ }
3166
+ function updateFormState() {
3167
+ const draftOverrides = getDraftOverrides();
3168
+ const hasDraftChanges = !overridesEqual(draftOverrides, appliedOverrides);
3169
+ applyButton.disabled = !hasDraftChanges || isSubmitting;
3170
+ revertButton.disabled = !hasDraftChanges || isSubmitting;
3171
+ clearButton.disabled = isSubmitting;
3172
+ formStatus.textContent = isSubmitting ? 'Applying...' : hasDraftChanges ? 'Unsaved changes' : hasOverridesValue(appliedOverrides) ? 'Overrides active' : 'No overrides';
3173
+ if (styles_components_module.overrideStatusDirty) formStatus.classList.toggle(styles_components_module.overrideStatusDirty, !isSubmitting && hasDraftChanges);
2842
3174
  }
2843
3175
  }
2844
3176
  function createOverrideInput(options) {
2845
- const { label, placeholder, value, onChange } = options;
2846
- let debounceTimer = null;
3177
+ const { label, placeholder, value } = options;
2847
3178
  const inputField = input({
2848
3179
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
2849
3180
  placeholder,
2850
- value,
2851
- onInput: (e)=>{
2852
- const target = e.target;
2853
- if (debounceTimer) clearTimeout(debounceTimer);
2854
- debounceTimer = setTimeout(()=>{
2855
- onChange(target.value);
2856
- }, 500);
2857
- }
2858
- });
2859
- return renderer_div({
2860
- style: {
2861
- display: 'flex',
2862
- alignItems: 'center',
2863
- justifyContent: 'space-between',
2864
- gap: '8px',
2865
- marginBottom: '8px'
2866
- },
2867
- children: [
2868
- renderer_div({
2869
- style: {
2870
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2871
- color: 'var(--c15t-devtools-text-muted)',
2872
- minWidth: '60px'
2873
- },
2874
- text: label
2875
- }),
2876
- renderer_div({
2877
- style: {
2878
- flex: '1'
2879
- },
2880
- children: [
2881
- inputField
2882
- ]
2883
- })
2884
- ]
3181
+ value
2885
3182
  });
3183
+ return {
3184
+ element: renderer_div({
3185
+ className: styles_components_module.overrideField,
3186
+ children: [
3187
+ renderer_span({
3188
+ className: styles_components_module.overrideLabel,
3189
+ text: label
3190
+ }),
3191
+ inputField
3192
+ ]
3193
+ }),
3194
+ control: inputField
3195
+ };
2886
3196
  }
2887
3197
  function createOverrideSelect(options) {
2888
- const { label, selectOptions, value, onChange } = options;
3198
+ const { label, selectOptions, value } = options;
2889
3199
  const selectField = renderer_select({
2890
3200
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
2891
3201
  options: selectOptions,
2892
- selectedValue: value,
2893
- onChange: (e)=>{
2894
- const target = e.target;
2895
- onChange(target.value);
2896
- }
2897
- });
2898
- return renderer_div({
2899
- style: {
2900
- display: 'flex',
2901
- alignItems: 'center',
2902
- justifyContent: 'space-between',
2903
- gap: '8px',
2904
- marginBottom: '8px'
2905
- },
2906
- children: [
2907
- renderer_div({
2908
- style: {
2909
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2910
- color: 'var(--c15t-devtools-text-muted)',
2911
- minWidth: '60px'
2912
- },
2913
- text: label
2914
- }),
2915
- renderer_div({
2916
- style: {
2917
- flex: '1'
2918
- },
2919
- children: [
2920
- selectField
2921
- ]
2922
- })
2923
- ]
3202
+ selectedValue: value
2924
3203
  });
3204
+ return {
3205
+ element: renderer_div({
3206
+ className: styles_components_module.overrideField,
3207
+ children: [
3208
+ renderer_span({
3209
+ className: styles_components_module.overrideLabel,
3210
+ text: label
3211
+ }),
3212
+ selectField
3213
+ ]
3214
+ }),
3215
+ control: selectField
3216
+ };
3217
+ }
3218
+ function getDraftFromOverrides(overrides) {
3219
+ return {
3220
+ country: overrides?.country ?? '',
3221
+ region: overrides?.region ?? '',
3222
+ language: overrides?.language ?? '',
3223
+ gpc: overrides?.gpc === true ? 'true' : overrides?.gpc === false ? 'false' : ''
3224
+ };
3225
+ }
3226
+ function normalizeOverrideDraft(draft) {
3227
+ return {
3228
+ country: normalizeAlphaCode(draft.country),
3229
+ region: normalizeAlphaCode(draft.region),
3230
+ language: normalizeLanguageCode(draft.language),
3231
+ gpc: 'true' === draft.gpc ? true : 'false' === draft.gpc ? false : void 0
3232
+ };
3233
+ }
3234
+ function normalizeAlphaCode(value) {
3235
+ const normalized = value.trim().toUpperCase();
3236
+ return normalized || void 0;
3237
+ }
3238
+ function normalizeLanguageCode(value) {
3239
+ const normalized = value.trim();
3240
+ return normalized || void 0;
3241
+ }
3242
+ function overridesEqual(a, b) {
3243
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
3244
+ }
3245
+ function hasOverridesValue(overrides) {
3246
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
2925
3247
  }
2926
3248
  const COUNTRY_OPTIONS = [
2927
3249
  {
@@ -3045,6 +3367,32 @@ const COUNTRY_OPTIONS = [
3045
3367
  label: 'ZA - South Africa'
3046
3368
  }
3047
3369
  ];
3370
+ const GPC_OPTIONS = [
3371
+ {
3372
+ value: '',
3373
+ label: '-- Browser Default --'
3374
+ },
3375
+ {
3376
+ value: 'true',
3377
+ label: 'Force On (Simulated)'
3378
+ },
3379
+ {
3380
+ value: 'false',
3381
+ label: 'Force Off (Simulated)'
3382
+ }
3383
+ ];
3384
+ function getEffectiveGpcLabel(gpcOverride) {
3385
+ if (true === gpcOverride) return 'On (Override)';
3386
+ if (false === gpcOverride) return 'Off (Override)';
3387
+ if ('undefined' == typeof window || 'undefined' == typeof navigator) return 'Unknown';
3388
+ try {
3389
+ const nav = navigator;
3390
+ const value = nav.globalPrivacyControl;
3391
+ return true === value || '1' === value ? 'Active' : 'Inactive';
3392
+ } catch {
3393
+ return 'Unknown';
3394
+ }
3395
+ }
3048
3396
  function getModelLabel(model) {
3049
3397
  switch(model){
3050
3398
  case 'opt-in':
@@ -3061,9 +3409,11 @@ function createCompactInfoCard(label, value) {
3061
3409
  return renderer_div({
3062
3410
  className: styles_components_module.gridCard ?? '',
3063
3411
  style: {
3412
+ padding: '6px 8px',
3413
+ minHeight: 'auto',
3064
3414
  flexDirection: 'column',
3065
3415
  alignItems: 'flex-start',
3066
- gap: '2px'
3416
+ gap: '1px'
3067
3417
  },
3068
3418
  children: [
3069
3419
  renderer_span({
@@ -3088,34 +3438,38 @@ const dismissedResources = new Set();
3088
3438
  function scanDOM(state) {
3089
3439
  const results = [];
3090
3440
  const configuredScripts = state.scripts || [];
3091
- const managedDomains = new Map();
3441
+ const managedResources = [];
3092
3442
  for (const script of configuredScripts)if (script.src) try {
3093
3443
  const url = new URL(script.src, window.location.origin);
3094
- if (url.hostname !== window.location.hostname) managedDomains.set(url.hostname, script.id);
3444
+ if (url.hostname !== window.location.hostname) managedResources.push({
3445
+ scriptId: script.id,
3446
+ domain: url.hostname,
3447
+ pathPrefix: normalizePathname(url.pathname)
3448
+ });
3095
3449
  } catch {}
3096
3450
  const scriptElements = document.querySelectorAll("script[src]");
3097
3451
  for (const el of scriptElements){
3098
3452
  const src = el.getAttribute('src');
3099
3453
  if (!src) continue;
3100
- const resource = checkResource(src, "script", managedDomains);
3454
+ const resource = checkResource(src, "script", managedResources);
3101
3455
  if (resource) results.push(resource);
3102
3456
  }
3103
3457
  const iframeElements = document.querySelectorAll('iframe[src]');
3104
3458
  for (const el of iframeElements){
3105
3459
  const src = el.getAttribute('src');
3106
3460
  if (!src) continue;
3107
- const resource = checkResource(src, 'iframe', managedDomains);
3461
+ const resource = checkResource(src, 'iframe', managedResources);
3108
3462
  if (resource) results.push(resource);
3109
3463
  }
3110
3464
  return results;
3111
3465
  }
3112
- function checkResource(src, type, managedDomains) {
3466
+ function checkResource(src, type, managedResources) {
3113
3467
  try {
3114
3468
  const url = new URL(src, window.location.origin);
3115
3469
  const domain = url.hostname;
3116
3470
  if (domain === window.location.hostname) return null;
3117
3471
  if ('data:' === url.protocol || 'blob:' === url.protocol) return null;
3118
- const managedBy = managedDomains.get(domain);
3472
+ const managedBy = findManagedScriptId(url, managedResources);
3119
3473
  const isManaged = Boolean(managedBy);
3120
3474
  return {
3121
3475
  type,
@@ -3127,6 +3481,21 @@ function checkResource(src, type, managedDomains) {
3127
3481
  } catch {}
3128
3482
  return null;
3129
3483
  }
3484
+ function findManagedScriptId(url, managedResources) {
3485
+ const domain = url.hostname;
3486
+ const path = normalizePathname(url.pathname);
3487
+ let bestMatch = null;
3488
+ for (const matcher of managedResources)if (matcher.domain === domain) {
3489
+ if ('/' === matcher.pathPrefix || path.startsWith(matcher.pathPrefix)) {
3490
+ if (!bestMatch || matcher.pathPrefix.length > bestMatch.pathPrefix.length) bestMatch = matcher;
3491
+ }
3492
+ }
3493
+ return bestMatch?.scriptId;
3494
+ }
3495
+ function normalizePathname(pathname) {
3496
+ const trimmed = pathname.trim();
3497
+ return trimmed.length > 0 ? trimmed : '/';
3498
+ }
3130
3499
  function createDomScannerSection(state) {
3131
3500
  let resultsContainer = null;
3132
3501
  let lastScanResults = [];
@@ -3291,7 +3660,7 @@ const CODE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" f
3291
3660
  <polyline points="8 6 2 12 8 18"></polyline>
3292
3661
  </svg>`;
3293
3662
  function scripts_renderScriptsPanel(container, options) {
3294
- const { getState } = options;
3663
+ const { getState, getEvents } = options;
3295
3664
  renderer_clearElement(container);
3296
3665
  const state = getState();
3297
3666
  if (!state) return void container.appendChild(renderer_div({
@@ -3306,6 +3675,7 @@ function scripts_renderScriptsPanel(container, options) {
3306
3675
  const scripts = state.scripts || [];
3307
3676
  const loadedScripts = state.loadedScripts || {};
3308
3677
  const networkBlocker = state.networkBlocker;
3678
+ const events = getEvents?.() ?? [];
3309
3679
  if (0 === scripts.length) {
3310
3680
  const scriptsSection = createSection({
3311
3681
  title: 'Configured Scripts',
@@ -3388,6 +3758,20 @@ function scripts_renderScriptsPanel(container, options) {
3388
3758
  ]
3389
3759
  });
3390
3760
  container.appendChild(networkSection);
3761
+ const blockedRequestEvents = events.filter((event)=>'network' === event.type);
3762
+ const networkEventsSection = createSection({
3763
+ title: `Blocked Requests (${blockedRequestEvents.length})`,
3764
+ children: 0 === blockedRequestEvents.length ? [
3765
+ renderer_div({
3766
+ style: {
3767
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3768
+ color: 'var(--c15t-devtools-text-muted)'
3769
+ },
3770
+ text: 'No blocked network requests recorded in this session'
3771
+ })
3772
+ ] : createBlockedRequestContent(blockedRequestEvents)
3773
+ });
3774
+ container.appendChild(networkEventsSection);
3391
3775
  const loadedCount = Object.values(loadedScripts).filter(Boolean).length;
3392
3776
  const totalCount = scripts.length;
3393
3777
  const summarySection = createSection({
@@ -3413,12 +3797,127 @@ function scripts_renderScriptsPanel(container, options) {
3413
3797
  }
3414
3798
  function checkScriptConsent(state, category) {
3415
3799
  if (!category) return true;
3800
+ if ('function' == typeof state.has) try {
3801
+ return state.has(category);
3802
+ } catch {}
3416
3803
  if ('string' == typeof category) {
3417
3804
  const consents = state.consents || {};
3418
3805
  return true === consents[category];
3419
3806
  }
3420
3807
  return false;
3421
3808
  }
3809
+ function createBlockedRequestContent(events) {
3810
+ const stats = new Map();
3811
+ for (const event of events){
3812
+ const ruleId = getEventRuleId(event) ?? 'unknown';
3813
+ stats.set(ruleId, (stats.get(ruleId) ?? 0) + 1);
3814
+ }
3815
+ const statsList = renderer_div({
3816
+ style: {
3817
+ display: 'flex',
3818
+ flexDirection: 'column',
3819
+ gap: '4px',
3820
+ marginBottom: '8px'
3821
+ },
3822
+ children: [
3823
+ ...stats.entries()
3824
+ ].sort((a, b)=>b[1] - a[1]).map(([ruleId, count])=>createInfoRow({
3825
+ label: 'unknown' === ruleId ? 'Unknown Rule' : `Rule: ${ruleId}`,
3826
+ value: `${count}`
3827
+ }))
3828
+ });
3829
+ const latestEvents = events.slice(0, 5);
3830
+ const latestList = renderer_div({
3831
+ style: {
3832
+ display: 'flex',
3833
+ flexDirection: 'column',
3834
+ gap: '4px'
3835
+ },
3836
+ children: latestEvents.map((event)=>createInfoRow({
3837
+ label: `${formatEventTime(event.timestamp)} ${getEventMethod(event)}`,
3838
+ value: scripts_truncateText(getEventUrl(event), 38)
3839
+ }))
3840
+ });
3841
+ return [
3842
+ statsList,
3843
+ latestList
3844
+ ];
3845
+ }
3846
+ function getEventRuleId(event) {
3847
+ const data = event.data;
3848
+ const rule = data?.rule;
3849
+ const ruleId = rule?.id ?? data?.ruleId;
3850
+ return 'string' == typeof ruleId || 'number' == typeof ruleId ? String(ruleId) : void 0;
3851
+ }
3852
+ function getEventMethod(event) {
3853
+ const data = event.data;
3854
+ const method = data?.method;
3855
+ return 'string' == typeof method ? method.toUpperCase() : 'REQ';
3856
+ }
3857
+ function getEventUrl(event) {
3858
+ const data = event.data;
3859
+ const url = data?.url;
3860
+ return 'string' == typeof url ? url : event.message;
3861
+ }
3862
+ function formatEventTime(timestamp) {
3863
+ return new Date(timestamp).toLocaleTimeString('en-US', {
3864
+ hour12: false,
3865
+ hour: '2-digit',
3866
+ minute: '2-digit',
3867
+ second: '2-digit'
3868
+ });
3869
+ }
3870
+ function scripts_truncateText(text, maxLength) {
3871
+ if (text.length <= maxLength) return text;
3872
+ return `${text.slice(0, maxLength - 3)}...`;
3873
+ }
3874
+ const DEVTOOLS_OVERRIDES_STORAGE_KEY = 'c15t-devtools-overrides';
3875
+ function normalizeStringValue(value) {
3876
+ if ('string' != typeof value) return;
3877
+ const normalized = value.trim();
3878
+ return normalized.length > 0 ? normalized : void 0;
3879
+ }
3880
+ function normalizeBooleanValue(value) {
3881
+ return 'boolean' == typeof value ? value : void 0;
3882
+ }
3883
+ function normalizeOverrides(value) {
3884
+ if (!value || 'object' != typeof value) return null;
3885
+ const source = value;
3886
+ const overrides = {
3887
+ country: normalizeStringValue(source.country),
3888
+ region: normalizeStringValue(source.region),
3889
+ language: normalizeStringValue(source.language),
3890
+ gpc: normalizeBooleanValue(source.gpc)
3891
+ };
3892
+ return hasPersistedOverrides(overrides) ? overrides : null;
3893
+ }
3894
+ function hasPersistedOverrides(overrides) {
3895
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3896
+ }
3897
+ function override_storage_loadPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3898
+ if ('undefined' == typeof window) return null;
3899
+ try {
3900
+ const stored = localStorage.getItem(storageKey);
3901
+ if (!stored) return null;
3902
+ const parsed = JSON.parse(stored);
3903
+ return normalizeOverrides(parsed);
3904
+ } catch {
3905
+ return null;
3906
+ }
3907
+ }
3908
+ function override_storage_persistOverrides(overrides, storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3909
+ if ('undefined' == typeof window) return;
3910
+ try {
3911
+ if (!hasPersistedOverrides(overrides)) return void localStorage.removeItem(storageKey);
3912
+ localStorage.setItem(storageKey, JSON.stringify(overrides));
3913
+ } catch {}
3914
+ }
3915
+ function override_storage_clearPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3916
+ if ('undefined' == typeof window) return;
3917
+ try {
3918
+ localStorage.removeItem(storageKey);
3919
+ } catch {}
3920
+ }
3422
3921
  const STORAGE_KEYS = {
3423
3922
  C15T: 'c15t',
3424
3923
  PENDING_SYNC: 'c15t:pending-consent-sync',
@@ -3556,12 +4055,37 @@ function store_connector_createStoreConnector(options = {}) {
3556
4055
  const { namespace = 'c15tStore', onConnect, onStateChange, onDisconnect } = options;
3557
4056
  let store = null;
3558
4057
  let unsubscribe = null;
3559
- let pollInterval = null;
4058
+ let reconnectTimeout = null;
4059
+ let reconnectAttempts = 0;
4060
+ let hasNotifiedDisconnect = false;
3560
4061
  const listeners = new Set();
4062
+ const INITIAL_RETRY_DELAY_MS = 100;
4063
+ const MAX_RETRY_DELAY_MS = 2000;
4064
+ const DISCONNECT_NOTIFY_ATTEMPTS = 5;
4065
+ function clearReconnectTimer() {
4066
+ if (reconnectTimeout) {
4067
+ clearTimeout(reconnectTimeout);
4068
+ reconnectTimeout = null;
4069
+ }
4070
+ }
4071
+ function resetReconnectState() {
4072
+ reconnectAttempts = 0;
4073
+ hasNotifiedDisconnect = false;
4074
+ }
4075
+ function notifyDisconnectedOnce() {
4076
+ if (hasNotifiedDisconnect) return;
4077
+ hasNotifiedDisconnect = true;
4078
+ onDisconnect?.();
4079
+ }
3561
4080
  function tryConnect() {
3562
4081
  if ('undefined' == typeof window) return false;
3563
4082
  const storeInstance = window[namespace];
3564
4083
  if (storeInstance && 'function' == typeof storeInstance.getState) {
4084
+ if (store === storeInstance && unsubscribe) return true;
4085
+ if (unsubscribe) {
4086
+ unsubscribe();
4087
+ unsubscribe = null;
4088
+ }
3565
4089
  store = storeInstance;
3566
4090
  unsubscribe = store.subscribe((state)=>{
3567
4091
  onStateChange?.(state);
@@ -3569,30 +4093,26 @@ function store_connector_createStoreConnector(options = {}) {
3569
4093
  });
3570
4094
  const currentState = store.getState();
3571
4095
  onConnect?.(currentState, store);
3572
- if (pollInterval) {
3573
- clearInterval(pollInterval);
3574
- pollInterval = null;
3575
- }
4096
+ clearReconnectTimer();
4097
+ resetReconnectState();
3576
4098
  return true;
3577
4099
  }
3578
4100
  return false;
3579
4101
  }
4102
+ function scheduleReconnect(immediate = false) {
4103
+ if (store || reconnectTimeout) return;
4104
+ const delay = immediate ? 0 : Math.min(INITIAL_RETRY_DELAY_MS * 2 ** Math.min(reconnectAttempts, 5), MAX_RETRY_DELAY_MS);
4105
+ reconnectTimeout = setTimeout(()=>{
4106
+ reconnectTimeout = null;
4107
+ reconnectAttempts++;
4108
+ if (tryConnect()) return;
4109
+ if (reconnectAttempts >= DISCONNECT_NOTIFY_ATTEMPTS) notifyDisconnectedOnce();
4110
+ scheduleReconnect();
4111
+ }, delay);
4112
+ }
3580
4113
  function startPolling() {
3581
- if (pollInterval) return;
3582
4114
  if (tryConnect()) return;
3583
- let attempts = 0;
3584
- const maxAttempts = 50;
3585
- pollInterval = setInterval(()=>{
3586
- attempts++;
3587
- if (tryConnect()) return;
3588
- if (attempts >= maxAttempts) {
3589
- if (pollInterval) {
3590
- clearInterval(pollInterval);
3591
- pollInterval = null;
3592
- }
3593
- onDisconnect?.();
3594
- }
3595
- }, 100);
4115
+ scheduleReconnect(true);
3596
4116
  }
3597
4117
  startPolling();
3598
4118
  return {
@@ -3606,11 +4126,13 @@ function store_connector_createStoreConnector(options = {}) {
3606
4126
  listeners.delete(listener);
3607
4127
  };
3608
4128
  },
4129
+ retryConnection: ()=>{
4130
+ if (store) return;
4131
+ resetReconnectState();
4132
+ scheduleReconnect(true);
4133
+ },
3609
4134
  destroy: ()=>{
3610
- if (pollInterval) {
3611
- clearInterval(pollInterval);
3612
- pollInterval = null;
3613
- }
4135
+ clearReconnectTimer();
3614
4136
  if (unsubscribe) {
3615
4137
  unsubscribe();
3616
4138
  unsubscribe = null;
@@ -3629,14 +4151,61 @@ tokens_options.domAPI = styleDomAPI_default();
3629
4151
  tokens_options.insertStyleElement = insertStyleElement_default();
3630
4152
  injectStylesIntoStyleTag_default()(tokens.A, tokens_options);
3631
4153
  tokens.A && tokens.A.locals && tokens.A.locals;
4154
+ function normalizeOverridesForPersistence(overrides) {
4155
+ return {
4156
+ country: overrides?.country?.trim() || void 0,
4157
+ region: overrides?.region?.trim() || void 0,
4158
+ language: overrides?.language?.trim() || void 0,
4159
+ gpc: overrides?.gpc
4160
+ };
4161
+ }
4162
+ function persistedOverridesEqual(a, b) {
4163
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
4164
+ }
4165
+ function getBlockedRequestMessage(payload) {
4166
+ const data = payload;
4167
+ const method = 'string' == typeof data?.method ? data.method.toUpperCase() : 'REQUEST';
4168
+ const url = 'string' == typeof data?.url ? data.url : 'unknown-url';
4169
+ return `Network blocked: ${method} ${url}`;
4170
+ }
3632
4171
  function createDevToolsPanel(options) {
3633
4172
  const { namespace = 'c15tStore' } = options;
4173
+ let originalEmbeddedNetworkBlockedCallback;
4174
+ let hasWrappedEmbeddedNetworkBlocker = false;
3634
4175
  const stateManager = state_manager_createStateManager({
3635
4176
  isOpen: true
3636
4177
  });
3637
4178
  const storeConnector = store_connector_createStoreConnector({
3638
4179
  namespace,
3639
- onConnect: ()=>stateManager.setConnected(true),
4180
+ onConnect: (state, store)=>{
4181
+ stateManager.setConnected(true);
4182
+ const currentNetworkBlocker = state.networkBlocker;
4183
+ if (currentNetworkBlocker && !hasWrappedEmbeddedNetworkBlocker) {
4184
+ originalEmbeddedNetworkBlockedCallback = currentNetworkBlocker.onRequestBlocked;
4185
+ hasWrappedEmbeddedNetworkBlocker = true;
4186
+ store.getState().setNetworkBlocker({
4187
+ ...currentNetworkBlocker,
4188
+ onRequestBlocked: (payload)=>{
4189
+ stateManager.addEvent({
4190
+ type: 'network',
4191
+ message: getBlockedRequestMessage(payload),
4192
+ data: payload
4193
+ });
4194
+ if ('function' == typeof originalEmbeddedNetworkBlockedCallback) originalEmbeddedNetworkBlockedCallback(payload);
4195
+ }
4196
+ });
4197
+ }
4198
+ const persistedOverrides = override_storage_loadPersistedOverrides();
4199
+ if (persistedOverrides) {
4200
+ const currentOverrides = normalizeOverridesForPersistence(state.overrides);
4201
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
4202
+ country: persistedOverrides.country,
4203
+ region: persistedOverrides.region,
4204
+ language: persistedOverrides.language,
4205
+ gpc: persistedOverrides.gpc
4206
+ });
4207
+ }
4208
+ },
3640
4209
  onDisconnect: ()=>stateManager.setConnected(false)
3641
4210
  });
3642
4211
  const container = renderer_div({
@@ -3685,29 +4254,61 @@ function createDevToolsPanel(options) {
3685
4254
  case 'location':
3686
4255
  location_renderLocationPanel(contentArea, {
3687
4256
  getState: getStoreState,
3688
- onSetOverrides: async (overrides)=>{
4257
+ onApplyOverrides: async (overrides)=>{
3689
4258
  const store = storeConnector.getStore();
3690
4259
  if (store) {
3691
- const current = store.getState().overrides || {};
3692
4260
  await store.getState().setOverrides({
3693
- ...current,
3694
- ...overrides
4261
+ country: overrides.country,
4262
+ region: overrides.region,
4263
+ language: overrides.language,
4264
+ gpc: overrides.gpc
4265
+ });
4266
+ override_storage_persistOverrides({
4267
+ country: overrides.country,
4268
+ region: overrides.region,
4269
+ language: overrides.language,
4270
+ gpc: overrides.gpc
3695
4271
  });
3696
4272
  }
3697
4273
  },
3698
4274
  onClearOverrides: async ()=>{
3699
- await storeConnector.getStore()?.getState().setOverrides(void 0);
4275
+ await storeConnector.getStore()?.getState().setOverrides({
4276
+ country: void 0,
4277
+ region: void 0,
4278
+ language: void 0,
4279
+ gpc: void 0
4280
+ });
4281
+ override_storage_clearPersistedOverrides();
3700
4282
  }
3701
4283
  });
3702
4284
  break;
3703
4285
  case "scripts":
3704
4286
  scripts_renderScriptsPanel(contentArea, {
3705
- getState: getStoreState
4287
+ getState: getStoreState,
4288
+ getEvents: ()=>stateManager.getState().eventLog
3706
4289
  });
3707
4290
  break;
3708
4291
  case 'iab':
3709
4292
  iab_renderIabPanel(contentArea, {
3710
4293
  getState: getStoreState,
4294
+ onSetPurposeConsent: (purposeId, value)=>{
4295
+ storeConnector.getStore()?.getState().iab?.setPurposeConsent(purposeId, value);
4296
+ },
4297
+ onSetVendorConsent: (vendorId, value)=>{
4298
+ storeConnector.getStore()?.getState().iab?.setVendorConsent(vendorId, value);
4299
+ },
4300
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
4301
+ storeConnector.getStore()?.getState().iab?.setSpecialFeatureOptIn(featureId, value);
4302
+ },
4303
+ onAcceptAll: ()=>{
4304
+ storeConnector.getStore()?.getState().iab?.acceptAll();
4305
+ },
4306
+ onRejectAll: ()=>{
4307
+ storeConnector.getStore()?.getState().iab?.rejectAll();
4308
+ },
4309
+ onSave: ()=>{
4310
+ storeConnector.getStore()?.getState().iab?.save();
4311
+ },
3711
4312
  onReset: async ()=>{
3712
4313
  const store = storeConnector.getStore();
3713
4314
  if (store) await reset_consents_resetAllConsents(store);
@@ -3768,6 +4369,14 @@ function createDevToolsPanel(options) {
3768
4369
  return {
3769
4370
  element: container,
3770
4371
  destroy: ()=>{
4372
+ const store = storeConnector.getStore();
4373
+ if (store && hasWrappedEmbeddedNetworkBlocker) {
4374
+ const currentNetworkBlocker = store.getState().networkBlocker;
4375
+ if (currentNetworkBlocker) store.getState().setNetworkBlocker({
4376
+ ...currentNetworkBlocker,
4377
+ onRequestBlocked: originalEmbeddedNetworkBlockedCallback
4378
+ });
4379
+ }
3771
4380
  unsubscribe();
3772
4381
  tabsInstance.destroy();
3773
4382
  storeConnector.destroy();