@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.cjs CHANGED
@@ -345,22 +345,26 @@ var __webpack_modules__ = {
345
345
  justify-content: center;
346
346
  align-items: center;
347
347
  gap: var(--c15t-space-xs, .25rem);
348
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
349
348
  border: 1px solid var(--c15t-border, #e3e3e3);
350
349
  border-radius: var(--c15t-radius-md, .5rem);
351
350
  background-color: var(--c15t-surface, #fff);
351
+ min-height: 30px;
352
352
  color: var(--c15t-text, #171717);
353
353
  font-family: inherit;
354
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
354
+ font-size: 12px;
355
355
  font-weight: var(--c15t-font-weight-medium, 500);
356
356
  cursor: pointer;
357
- 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));
357
+ 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));
358
+ padding: 5px 10px;
359
+ line-height: 1;
358
360
  display: inline-flex;
361
+ box-shadow: 0 1px 1px #0000000a;
359
362
  }
360
363
 
361
364
  .btn-evRVlh:hover {
362
365
  background-color: var(--c15t-surface-hover, #f7f7f7);
363
366
  border-color: var(--c15t-border-hover, #c9c9c9);
367
+ box-shadow: 0 2px 6px #00000014;
364
368
  }
365
369
 
366
370
  .btn-evRVlh:focus-visible {
@@ -368,9 +372,14 @@ var __webpack_modules__ = {
368
372
  outline-offset: 1px;
369
373
  }
370
374
 
375
+ .btn-evRVlh:active {
376
+ box-shadow: 0 1px 2px #00000014;
377
+ }
378
+
371
379
  .btn-evRVlh:disabled {
372
380
  opacity: .5;
373
381
  cursor: not-allowed;
382
+ box-shadow: none;
374
383
  }
375
384
 
376
385
  .btnPrimary-dA6nqY {
@@ -396,8 +405,10 @@ var __webpack_modules__ = {
396
405
  }
397
406
 
398
407
  .btnSmall-TjXoqZ {
399
- padding: 2px var(--c15t-space-xs, .25rem);
400
- font-size: 10px;
408
+ border-radius: var(--c15t-radius-sm, .375rem);
409
+ min-height: 26px;
410
+ padding: 3px 8px;
411
+ font-size: 11px;
401
412
  }
402
413
 
403
414
  .btnIcon-fiYQAh {
@@ -549,6 +560,50 @@ var __webpack_modules__ = {
549
560
  letter-spacing: .5px;
550
561
  }
551
562
 
563
+ .overrideField-keNdpJ {
564
+ flex-direction: column;
565
+ gap: 3px;
566
+ margin-bottom: 0;
567
+ display: flex;
568
+ }
569
+
570
+ .overrideLabel-ApMoTw {
571
+ color: var(--c15t-text-muted, #737373);
572
+ font-size: 11px;
573
+ font-weight: 600;
574
+ }
575
+
576
+ .overrideHint-yCfwGt {
577
+ color: var(--c15t-devtools-text-muted, #737373);
578
+ margin-top: 6px;
579
+ font-size: 11px;
580
+ }
581
+
582
+ .overrideActions-imdcn7 {
583
+ border-top: 1px dashed var(--c15t-border, #e3e3e3);
584
+ justify-content: space-between;
585
+ align-items: center;
586
+ gap: 8px;
587
+ margin-top: 8px;
588
+ padding-top: 8px;
589
+ display: flex;
590
+ }
591
+
592
+ .overrideActionButtons-gYOx1e {
593
+ flex-wrap: wrap;
594
+ gap: 6px;
595
+ display: flex;
596
+ }
597
+
598
+ .overrideStatus-sty_qS {
599
+ color: var(--c15t-text-muted, #737373);
600
+ font-size: 11px;
601
+ }
602
+
603
+ .overrideStatusDirty-OUdDMw {
604
+ color: var(--c15t-devtools-badge-warning, #f59f0a);
605
+ }
606
+
552
607
  .infoRow-RlB_0h {
553
608
  padding: var(--c15t-space-xs, .25rem) 0;
554
609
  justify-content: space-between;
@@ -641,6 +696,13 @@ var __webpack_modules__ = {
641
696
  section: "section-a197cB",
642
697
  sectionHeader: "sectionHeader-Xcljcw",
643
698
  sectionTitle: "sectionTitle-RUiFld",
699
+ overrideField: "overrideField-keNdpJ",
700
+ overrideLabel: "overrideLabel-ApMoTw",
701
+ overrideHint: "overrideHint-yCfwGt",
702
+ overrideActions: "overrideActions-imdcn7",
703
+ overrideActionButtons: "overrideActionButtons-gYOx1e",
704
+ overrideStatus: "overrideStatus-sty_qS",
705
+ overrideStatusDirty: "overrideStatusDirty-OUdDMw",
644
706
  infoRow: "infoRow-RlB_0h",
645
707
  infoLabel: "infoLabel-_pbK33",
646
708
  infoValue: "infoValue-flMl_e",
@@ -1123,16 +1185,23 @@ var __webpack_modules__ = {
1123
1185
  ___CSS_LOADER_EXPORT___.push([
1124
1186
  module.id,
1125
1187
  `.tabList-IyuiBE {
1188
+ align-items: center;
1126
1189
  gap: var(--c15t-space-xs, .25rem);
1127
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
1190
+ padding: var(--c15t-space-sm, .5rem) var(--c15t-space-sm, .5rem);
1128
1191
  border-bottom: 1px solid var(--c15t-border, #e3e3e3);
1129
1192
  background-color: var(--c15t-surface, #fff);
1130
1193
  scrollbar-width: none;
1131
1194
  -ms-overflow-style: none;
1195
+ scroll-padding-inline-end: var(--c15t-space-sm, .5rem);
1132
1196
  display: flex;
1133
1197
  overflow-x: auto;
1134
1198
  }
1135
1199
 
1200
+ .tabList-IyuiBE:after {
1201
+ content: "";
1202
+ flex: 0 0 var(--c15t-space-sm, .5rem);
1203
+ }
1204
+
1136
1205
  .tabList-IyuiBE::-webkit-scrollbar {
1137
1206
  display: none;
1138
1207
  }
@@ -1140,17 +1209,17 @@ var __webpack_modules__ = {
1140
1209
  .tab-yfDEqg {
1141
1210
  align-items: center;
1142
1211
  gap: var(--c15t-space-xs, .25rem);
1143
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
1144
1212
  border-radius: var(--c15t-radius-md, .5rem);
1145
1213
  color: var(--c15t-text-muted, #737373);
1146
1214
  font-family: inherit;
1147
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
1215
+ font-size: 11px;
1148
1216
  font-weight: var(--c15t-font-weight-medium, 500);
1149
1217
  cursor: pointer;
1150
1218
  white-space: nowrap;
1151
1219
  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));
1152
1220
  background-color: #0000;
1153
1221
  border: none;
1222
+ padding: 3px 7px;
1154
1223
  display: flex;
1155
1224
  }
1156
1225
 
@@ -2301,13 +2370,13 @@ var __webpack_exports__ = {};
2301
2370
  },
2302
2371
  children: [
2303
2372
  createButton({
2304
- text: 'All',
2373
+ text: 'Accept',
2305
2374
  variant: 'primary',
2306
2375
  small: true,
2307
2376
  onClick: onAcceptAll
2308
2377
  }),
2309
2378
  createButton({
2310
- text: 'None',
2379
+ text: 'Reject',
2311
2380
  variant: 'default',
2312
2381
  small: true,
2313
2382
  onClick: onRejectAll
@@ -2355,16 +2424,22 @@ var __webpack_exports__ = {};
2355
2424
  function formatConsentName(name) {
2356
2425
  return name.replace(/_/g, ' ').replace(/\b\w/g, (l)=>l.toUpperCase());
2357
2426
  }
2427
+ let activeFilter = 'all';
2428
+ let selectedEventId = null;
2358
2429
  function events_renderEventsPanel(container, options) {
2359
2430
  const { getEvents, onClear } = options;
2360
2431
  renderer_clearElement(container);
2361
- const events = getEvents();
2432
+ const allEvents = getEvents();
2433
+ const events = allEvents.filter((event)=>matchesFilter(event, activeFilter));
2434
+ if (!events.some((event)=>event.id === selectedEventId)) selectedEventId = events[0]?.id ?? null;
2435
+ const selectedEvent = events.find((event)=>event.id === selectedEventId) ?? null;
2362
2436
  const header = renderer_div({
2363
2437
  style: {
2364
2438
  display: 'flex',
2365
2439
  alignItems: 'center',
2366
2440
  justifyContent: 'space-between',
2367
- padding: '12px 16px 8px'
2441
+ padding: '12px 16px 8px',
2442
+ gap: '8px'
2368
2443
  },
2369
2444
  children: [
2370
2445
  renderer_span({
@@ -2375,44 +2450,130 @@ var __webpack_exports__ = {};
2375
2450
  textTransform: 'uppercase',
2376
2451
  letterSpacing: '0.5px'
2377
2452
  },
2378
- text: `Events (${events.length})`
2453
+ text: `Events (${events.length}/${allEvents.length})`
2379
2454
  }),
2380
- createButton({
2381
- text: 'Clear',
2382
- small: true,
2383
- onClick: onClear
2455
+ renderer_div({
2456
+ style: {
2457
+ display: 'flex',
2458
+ gap: '6px'
2459
+ },
2460
+ children: [
2461
+ createButton({
2462
+ text: 'Export',
2463
+ small: true,
2464
+ onClick: ()=>exportEvents(allEvents)
2465
+ }),
2466
+ createButton({
2467
+ text: 'Clear',
2468
+ small: true,
2469
+ onClick: ()=>{
2470
+ onClear();
2471
+ selectedEventId = null;
2472
+ events_renderEventsPanel(container, options);
2473
+ }
2474
+ })
2475
+ ]
2384
2476
  })
2385
2477
  ]
2386
2478
  });
2387
2479
  container.appendChild(header);
2480
+ container.appendChild(renderer_div({
2481
+ style: {
2482
+ display: 'flex',
2483
+ flexWrap: 'wrap',
2484
+ gap: '6px',
2485
+ padding: '0 16px 8px'
2486
+ },
2487
+ children: EVENT_FILTERS.map((filter)=>createFilterButton(filter, filter === activeFilter, ()=>{
2488
+ activeFilter = filter;
2489
+ selectedEventId = null;
2490
+ events_renderEventsPanel(container, options);
2491
+ }))
2492
+ }));
2388
2493
  const eventList = renderer_div({
2389
2494
  style: {
2390
2495
  display: 'flex',
2391
2496
  flexDirection: 'column',
2392
2497
  gap: '4px',
2393
2498
  padding: '0 12px 12px',
2394
- maxHeight: '400px',
2499
+ maxHeight: '300px',
2395
2500
  overflowY: 'auto'
2396
2501
  }
2397
2502
  });
2398
- if (0 === events.length) {
2399
- const emptyState = renderer_div({
2400
- style: {
2401
- padding: '32px 16px',
2402
- textAlign: 'center',
2403
- color: 'var(--c15t-text-muted)',
2404
- fontSize: 'var(--c15t-devtools-font-size-sm)'
2405
- },
2406
- text: 'No events recorded yet'
2407
- });
2408
- eventList.appendChild(emptyState);
2409
- } else for (const event of events){
2410
- const eventItem = createEventItem(event);
2411
- eventList.appendChild(eventItem);
2412
- }
2503
+ if (0 === events.length) eventList.appendChild(renderer_div({
2504
+ style: {
2505
+ padding: '20px 16px',
2506
+ textAlign: 'center',
2507
+ color: 'var(--c15t-text-muted)',
2508
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
2509
+ },
2510
+ text: 'No events match this filter'
2511
+ }));
2512
+ else for (const event of events)eventList.appendChild(createEventItem(event, event.id === selectedEventId, ()=>{
2513
+ selectedEventId = event.id;
2514
+ events_renderEventsPanel(container, options);
2515
+ }));
2413
2516
  container.appendChild(eventList);
2517
+ container.appendChild(createPayloadSection(selectedEvent));
2518
+ }
2519
+ const EVENT_FILTERS = [
2520
+ 'all',
2521
+ 'error',
2522
+ 'consent',
2523
+ 'network',
2524
+ 'iab'
2525
+ ];
2526
+ function createFilterButton(filter, active, onClick) {
2527
+ return createButton({
2528
+ text: filter.toUpperCase(),
2529
+ small: true,
2530
+ variant: active ? 'primary' : 'default',
2531
+ onClick
2532
+ });
2414
2533
  }
2415
- function createEventItem(event) {
2534
+ function matchesFilter(event, filter) {
2535
+ if ('all' === filter) return true;
2536
+ if ('error' === filter) return 'error' === event.type;
2537
+ if ('consent' === filter) return 'consent_set' === event.type || 'consent_save' === event.type || 'consent_reset' === event.type;
2538
+ if ('network' === filter) return 'network' === event.type;
2539
+ return 'iab' === event.type;
2540
+ }
2541
+ function createPayloadSection(event) {
2542
+ const payload = event?.data ? JSON.stringify(event.data, null, 2) : null;
2543
+ return renderer_div({
2544
+ style: {
2545
+ padding: '0 12px 12px'
2546
+ },
2547
+ children: [
2548
+ renderer_div({
2549
+ style: {
2550
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
2551
+ fontWeight: '600',
2552
+ color: 'var(--c15t-text-muted)',
2553
+ textTransform: 'uppercase',
2554
+ letterSpacing: '0.5px',
2555
+ marginBottom: '6px'
2556
+ },
2557
+ text: 'Payload'
2558
+ }),
2559
+ renderer_div({
2560
+ className: styles_components_module.gridCard ?? '',
2561
+ style: {
2562
+ padding: '8px',
2563
+ fontFamily: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
2564
+ fontSize: '11px',
2565
+ color: 'var(--c15t-text-muted)',
2566
+ maxHeight: '140px',
2567
+ overflowY: 'auto',
2568
+ whiteSpace: 'pre-wrap',
2569
+ wordBreak: 'break-word'
2570
+ },
2571
+ text: payload || 'Select an event with payload data'
2572
+ })
2573
+ ]
2574
+ });
2575
+ }
2576
+ function createEventItem(event, selected, onSelect) {
2416
2577
  const time = formatTime(event.timestamp);
2417
2578
  const icon = getEventIcon(event.type);
2418
2579
  const color = getEventColor(event.type);
@@ -2423,8 +2584,11 @@ var __webpack_exports__ = {};
2423
2584
  alignItems: 'center',
2424
2585
  gap: '8px',
2425
2586
  padding: '6px 10px',
2426
- fontSize: 'var(--c15t-devtools-font-size-xs)'
2587
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
2588
+ cursor: 'pointer',
2589
+ borderColor: selected ? 'var(--c15t-devtools-badge-info, #3b82f6)' : 'var(--c15t-border)'
2427
2590
  },
2591
+ onClick: onSelect,
2428
2592
  children: [
2429
2593
  renderer_span({
2430
2594
  style: {
@@ -2453,6 +2617,21 @@ var __webpack_exports__ = {};
2453
2617
  ]
2454
2618
  });
2455
2619
  }
2620
+ function exportEvents(events) {
2621
+ const json = JSON.stringify(events, null, 2);
2622
+ const blob = new Blob([
2623
+ json
2624
+ ], {
2625
+ type: 'application/json'
2626
+ });
2627
+ const url = URL.createObjectURL(blob);
2628
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2629
+ const a = document.createElement('a');
2630
+ a.href = url;
2631
+ a.download = `c15t-events-${timestamp}.json`;
2632
+ a.click();
2633
+ URL.revokeObjectURL(url);
2634
+ }
2456
2635
  function formatTime(timestamp) {
2457
2636
  const date = new Date(timestamp);
2458
2637
  return date.toLocaleTimeString('en-US', {
@@ -2471,7 +2650,10 @@ var __webpack_exports__ = {};
2471
2650
  return '○';
2472
2651
  case 'error':
2473
2652
  return '✕';
2474
- case 'info':
2653
+ case 'network':
2654
+ return '◉';
2655
+ case 'iab':
2656
+ return '◆';
2475
2657
  default:
2476
2658
  return '○';
2477
2659
  }
@@ -2485,13 +2667,16 @@ var __webpack_exports__ = {};
2485
2667
  return 'var(--c15t-devtools-badge-warning, #f59e0b)';
2486
2668
  case 'error':
2487
2669
  return 'var(--c15t-devtools-badge-error, #ef4444)';
2488
- case 'info':
2670
+ case 'network':
2671
+ return 'var(--c15t-devtools-badge-warning, #f59e0b)';
2672
+ case 'iab':
2673
+ return 'var(--c15t-devtools-badge-info, #3b82f6)';
2489
2674
  default:
2490
2675
  return 'var(--c15t-text-muted)';
2491
2676
  }
2492
2677
  }
2493
2678
  function iab_renderIabPanel(container, options) {
2494
- const { getState, onReset } = options;
2679
+ const { getState, onSetPurposeConsent, onSetVendorConsent, onSetSpecialFeatureOptIn, onAcceptAll, onRejectAll, onSave, onReset } = options;
2495
2680
  renderer_clearElement(container);
2496
2681
  const state = getState();
2497
2682
  if (!state) return void container.appendChild(renderer_div({
@@ -2569,7 +2754,9 @@ var __webpack_exports__ = {};
2569
2754
  for (const [purposeId, consent] of purposeEntries){
2570
2755
  const purposeInfo = purposes[purposeId];
2571
2756
  const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
2572
- purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent)));
2757
+ purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent), (value)=>{
2758
+ onSetPurposeConsent(Number(purposeId), value);
2759
+ }));
2573
2760
  }
2574
2761
  const purposesSection = createSection({
2575
2762
  title: `Purposes (${purposeEntries.length})`,
@@ -2595,7 +2782,9 @@ var __webpack_exports__ = {};
2595
2782
  for (const [featureId, optIn] of specialFeatureEntries){
2596
2783
  const featureInfo = specialFeatures[featureId];
2597
2784
  const featureName = featureInfo?.name || `Special Feature ${featureId}`;
2598
- specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn)));
2785
+ specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn), (value)=>{
2786
+ onSetSpecialFeatureOptIn(Number(featureId), value);
2787
+ }, 'feature'));
2599
2788
  }
2600
2789
  const specialFeaturesSection = createSection({
2601
2790
  title: `Special Features (${specialFeatureEntries.length})`,
@@ -2635,7 +2824,9 @@ var __webpack_exports__ = {};
2635
2824
  overflowY: 'auto'
2636
2825
  }
2637
2826
  });
2638
- for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab'));
2827
+ for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab', (value)=>{
2828
+ onSetVendorConsent(Number(vendorId), value);
2829
+ }));
2639
2830
  const vendorsSection = createSection({
2640
2831
  title: `IAB Vendors (${iabVendors.length})`,
2641
2832
  children: [
@@ -2654,7 +2845,9 @@ var __webpack_exports__ = {};
2654
2845
  overflowY: 'auto'
2655
2846
  }
2656
2847
  });
2657
- for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom'));
2848
+ for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom', (value)=>{
2849
+ onSetVendorConsent(vendorId, value);
2850
+ }));
2658
2851
  const customVendorsSection = createSection({
2659
2852
  title: `Custom Vendors (${customVendors.length})`,
2660
2853
  children: [
@@ -2676,15 +2869,40 @@ var __webpack_exports__ = {};
2676
2869
  style: {
2677
2870
  display: 'flex',
2678
2871
  alignItems: 'center',
2679
- justifyContent: 'flex-end',
2872
+ justifyContent: 'space-between',
2680
2873
  padding: '12px 16px',
2681
2874
  marginTop: 'auto',
2682
2875
  borderTop: '1px solid var(--c15t-border)',
2683
2876
  backgroundColor: 'var(--c15t-surface)'
2684
2877
  },
2685
2878
  children: [
2879
+ renderer_div({
2880
+ style: {
2881
+ display: 'flex',
2882
+ gap: '6px'
2883
+ },
2884
+ children: [
2885
+ createButton({
2886
+ text: 'Accept All',
2887
+ variant: 'primary',
2888
+ small: true,
2889
+ onClick: onAcceptAll
2890
+ }),
2891
+ createButton({
2892
+ text: 'Reject All',
2893
+ small: true,
2894
+ onClick: onRejectAll
2895
+ }),
2896
+ createButton({
2897
+ text: 'Save',
2898
+ variant: 'primary',
2899
+ small: true,
2900
+ onClick: onSave
2901
+ })
2902
+ ]
2903
+ }),
2686
2904
  createButton({
2687
- text: 'Reset All',
2905
+ text: 'Reset',
2688
2906
  variant: 'danger',
2689
2907
  small: true,
2690
2908
  onClick: onReset
@@ -2693,7 +2911,7 @@ var __webpack_exports__ = {};
2693
2911
  });
2694
2912
  container.appendChild(footer);
2695
2913
  }
2696
- function createPurposeRow(id, name, consent) {
2914
+ function createPurposeRow(id, name, consent, onChange, ariaKind = 'purpose') {
2697
2915
  return renderer_div({
2698
2916
  style: {
2699
2917
  display: 'flex',
@@ -2716,14 +2934,28 @@ var __webpack_exports__ = {};
2716
2934
  text: `${id}. ${name}`,
2717
2935
  title: name
2718
2936
  }),
2719
- createBadge({
2720
- text: consent ? '✓' : '✕',
2721
- variant: consent ? 'success' : 'error'
2937
+ renderer_div({
2938
+ style: {
2939
+ display: 'flex',
2940
+ alignItems: 'center',
2941
+ gap: '6px'
2942
+ },
2943
+ children: [
2944
+ createBadge({
2945
+ text: consent ? '✓' : '✕',
2946
+ variant: consent ? 'success' : 'error'
2947
+ }),
2948
+ createToggle({
2949
+ checked: consent,
2950
+ onChange,
2951
+ ariaLabel: `Toggle ${ariaKind} ${id}`
2952
+ })
2953
+ ]
2722
2954
  })
2723
2955
  ]
2724
2956
  });
2725
2957
  }
2726
- function createVendorRow(id, name, consent, type) {
2958
+ function createVendorRow(id, name, consent, type, onChange) {
2727
2959
  return renderer_div({
2728
2960
  style: {
2729
2961
  display: 'flex',
@@ -2770,16 +3002,21 @@ var __webpack_exports__ = {};
2770
3002
  createBadge({
2771
3003
  text: consent ? '✓' : '✕',
2772
3004
  variant: consent ? 'success' : 'error'
3005
+ }),
3006
+ createToggle({
3007
+ checked: consent,
3008
+ onChange,
3009
+ ariaLabel: `Toggle vendor ${id}`
2773
3010
  })
2774
3011
  ]
2775
3012
  });
2776
3013
  }
2777
3014
  function truncateText(text, maxLength) {
2778
3015
  if (text.length <= maxLength) return text;
2779
- return text.slice(0, maxLength - 3) + '...';
3016
+ return `${text.slice(0, maxLength - 3)}...`;
2780
3017
  }
2781
3018
  function location_renderLocationPanel(container, options) {
2782
- const { getState, onSetOverrides, onClearOverrides } = options;
3019
+ const { getState, onApplyOverrides, onClearOverrides } = options;
2783
3020
  renderer_clearElement(container);
2784
3021
  const state = getState();
2785
3022
  if (!state) return void container.appendChild(renderer_div({
@@ -2800,145 +3037,230 @@ var __webpack_exports__ = {};
2800
3037
  createCompactInfoCard('Jurisdiction', locationInfo?.jurisdiction || '—'),
2801
3038
  createCompactInfoCard('Language', translationConfig?.defaultLanguage || '—')
2802
3039
  ];
3040
+ gridItems.push(createCompactInfoCard('GPC', getEffectiveGpcLabel(overrides?.gpc)));
2803
3041
  if (state.model) gridItems.push(createCompactInfoCard('Model', getModelLabel(state.model)));
2804
3042
  const locationGrid = createGrid({
2805
- columns: 2,
3043
+ columns: 3,
2806
3044
  children: gridItems
2807
3045
  });
2808
3046
  container.appendChild(locationGrid);
3047
+ const initialDraft = getDraftFromOverrides(overrides);
3048
+ let appliedOverrides = normalizeOverrideDraft(initialDraft);
3049
+ let isSubmitting = false;
3050
+ const countryField = createOverrideSelect({
3051
+ label: 'Country',
3052
+ selectOptions: COUNTRY_OPTIONS,
3053
+ value: initialDraft.country
3054
+ });
3055
+ const regionField = createOverrideInput({
3056
+ label: 'Region',
3057
+ placeholder: 'e.g., CA, NY, BE',
3058
+ value: initialDraft.region
3059
+ });
3060
+ const languageField = createOverrideInput({
3061
+ label: 'Language',
3062
+ placeholder: 'e.g., de, fr, en-US',
3063
+ value: initialDraft.language
3064
+ });
3065
+ const gpcField = createOverrideSelect({
3066
+ label: 'GPC',
3067
+ selectOptions: GPC_OPTIONS,
3068
+ value: initialDraft.gpc
3069
+ });
3070
+ const formStatus = renderer_span({
3071
+ className: styles_components_module.overrideStatus,
3072
+ text: 'In sync'
3073
+ });
3074
+ const applyButton = createButton({
3075
+ text: 'Apply',
3076
+ variant: 'primary',
3077
+ small: true,
3078
+ disabled: true,
3079
+ onClick: ()=>{
3080
+ applyDraft();
3081
+ }
3082
+ });
3083
+ const revertButton = createButton({
3084
+ text: 'Revert',
3085
+ small: true,
3086
+ disabled: true,
3087
+ onClick: ()=>{
3088
+ setDraftValues(getDraftFromOverrides(appliedOverrides));
3089
+ updateFormState();
3090
+ }
3091
+ });
3092
+ const clearButton = createButton({
3093
+ text: 'Clear',
3094
+ small: true,
3095
+ onClick: ()=>{
3096
+ clearDraftAndOverrides();
3097
+ }
3098
+ });
3099
+ const overrideFieldsGrid = renderer_div({
3100
+ style: {
3101
+ display: 'grid',
3102
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
3103
+ gap: '8px 10px'
3104
+ },
3105
+ children: [
3106
+ countryField.element,
3107
+ regionField.element,
3108
+ languageField.element,
3109
+ gpcField.element
3110
+ ]
3111
+ });
2809
3112
  const overrideSection = createSection({
2810
3113
  title: 'Override Settings',
2811
- actions: [
2812
- createButton({
2813
- text: 'Clear',
2814
- small: true,
2815
- onClick: onClearOverrides
2816
- })
2817
- ],
2818
3114
  children: [
2819
- createOverrideSelect({
2820
- label: 'Country',
2821
- selectOptions: COUNTRY_OPTIONS,
2822
- value: overrides?.country || '',
2823
- onChange: (value)=>onSetOverrides({
2824
- country: value || void 0
2825
- })
2826
- }),
2827
- createOverrideInput({
2828
- label: 'Region',
2829
- placeholder: 'e.g., CA, NY, BE',
2830
- value: overrides?.region || '',
2831
- onChange: (value)=>onSetOverrides({
2832
- region: value || void 0
2833
- })
3115
+ overrideFieldsGrid,
3116
+ renderer_span({
3117
+ className: styles_components_module.overrideHint,
3118
+ text: 'GPC override only affects opt-out or unregulated jurisdictions.'
2834
3119
  }),
2835
- createOverrideInput({
2836
- label: 'Language',
2837
- placeholder: 'e.g., de, fr, en',
2838
- value: overrides?.language || '',
2839
- onChange: (value)=>onSetOverrides({
2840
- language: value || void 0
2841
- })
3120
+ renderer_div({
3121
+ className: styles_components_module.overrideActions,
3122
+ children: [
3123
+ renderer_div({
3124
+ className: styles_components_module.overrideActionButtons,
3125
+ children: [
3126
+ revertButton,
3127
+ applyButton,
3128
+ clearButton
3129
+ ]
3130
+ }),
3131
+ formStatus
3132
+ ]
2842
3133
  })
2843
3134
  ]
2844
3135
  });
2845
3136
  container.appendChild(overrideSection);
2846
- const hasOverrides = overrides && (overrides.country || overrides.region || overrides.language);
2847
- if (hasOverrides) {
2848
- const overrideBanner = renderer_div({
2849
- style: {
2850
- padding: '8px 16px',
2851
- backgroundColor: 'var(--c15t-devtools-badge-info-bg)',
2852
- color: 'var(--c15t-devtools-badge-info)',
2853
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2854
- borderTop: '1px solid var(--c15t-devtools-border)'
2855
- },
2856
- text: 'Overrides are active. This may affect consent behavior.'
3137
+ countryField.control.addEventListener('change', updateFormState);
3138
+ regionField.control.addEventListener('input', updateFormState);
3139
+ languageField.control.addEventListener('input', updateFormState);
3140
+ gpcField.control.addEventListener('change', updateFormState);
3141
+ updateFormState();
3142
+ async function applyDraft() {
3143
+ if (isSubmitting) return;
3144
+ const draftOverrides = getDraftOverrides();
3145
+ if (overridesEqual(draftOverrides, appliedOverrides)) return;
3146
+ isSubmitting = true;
3147
+ updateFormState();
3148
+ try {
3149
+ await onApplyOverrides(draftOverrides);
3150
+ appliedOverrides = draftOverrides;
3151
+ } finally{
3152
+ isSubmitting = false;
3153
+ updateFormState();
3154
+ }
3155
+ }
3156
+ async function clearDraftAndOverrides() {
3157
+ if (isSubmitting) return;
3158
+ isSubmitting = true;
3159
+ updateFormState();
3160
+ try {
3161
+ await onClearOverrides();
3162
+ appliedOverrides = {};
3163
+ setDraftValues(getDraftFromOverrides(void 0));
3164
+ } finally{
3165
+ isSubmitting = false;
3166
+ updateFormState();
3167
+ }
3168
+ }
3169
+ function getDraftOverrides() {
3170
+ return normalizeOverrideDraft({
3171
+ country: countryField.control.value,
3172
+ region: regionField.control.value,
3173
+ language: languageField.control.value,
3174
+ gpc: gpcField.control.value
2857
3175
  });
2858
- container.appendChild(overrideBanner);
3176
+ }
3177
+ function setDraftValues(draft) {
3178
+ countryField.control.value = draft.country;
3179
+ regionField.control.value = draft.region;
3180
+ languageField.control.value = draft.language;
3181
+ gpcField.control.value = draft.gpc;
3182
+ }
3183
+ function updateFormState() {
3184
+ const draftOverrides = getDraftOverrides();
3185
+ const hasDraftChanges = !overridesEqual(draftOverrides, appliedOverrides);
3186
+ applyButton.disabled = !hasDraftChanges || isSubmitting;
3187
+ revertButton.disabled = !hasDraftChanges || isSubmitting;
3188
+ clearButton.disabled = isSubmitting;
3189
+ formStatus.textContent = isSubmitting ? 'Applying...' : hasDraftChanges ? 'Unsaved changes' : hasOverridesValue(appliedOverrides) ? 'Overrides active' : 'No overrides';
3190
+ if (styles_components_module.overrideStatusDirty) formStatus.classList.toggle(styles_components_module.overrideStatusDirty, !isSubmitting && hasDraftChanges);
2859
3191
  }
2860
3192
  }
2861
3193
  function createOverrideInput(options) {
2862
- const { label, placeholder, value, onChange } = options;
2863
- let debounceTimer = null;
3194
+ const { label, placeholder, value } = options;
2864
3195
  const inputField = input({
2865
3196
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
2866
3197
  placeholder,
2867
- value,
2868
- onInput: (e)=>{
2869
- const target = e.target;
2870
- if (debounceTimer) clearTimeout(debounceTimer);
2871
- debounceTimer = setTimeout(()=>{
2872
- onChange(target.value);
2873
- }, 500);
2874
- }
2875
- });
2876
- return renderer_div({
2877
- style: {
2878
- display: 'flex',
2879
- alignItems: 'center',
2880
- justifyContent: 'space-between',
2881
- gap: '8px',
2882
- marginBottom: '8px'
2883
- },
2884
- children: [
2885
- renderer_div({
2886
- style: {
2887
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2888
- color: 'var(--c15t-devtools-text-muted)',
2889
- minWidth: '60px'
2890
- },
2891
- text: label
2892
- }),
2893
- renderer_div({
2894
- style: {
2895
- flex: '1'
2896
- },
2897
- children: [
2898
- inputField
2899
- ]
2900
- })
2901
- ]
3198
+ value
2902
3199
  });
3200
+ return {
3201
+ element: renderer_div({
3202
+ className: styles_components_module.overrideField,
3203
+ children: [
3204
+ renderer_span({
3205
+ className: styles_components_module.overrideLabel,
3206
+ text: label
3207
+ }),
3208
+ inputField
3209
+ ]
3210
+ }),
3211
+ control: inputField
3212
+ };
2903
3213
  }
2904
3214
  function createOverrideSelect(options) {
2905
- const { label, selectOptions, value, onChange } = options;
3215
+ const { label, selectOptions, value } = options;
2906
3216
  const selectField = renderer_select({
2907
3217
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
2908
3218
  options: selectOptions,
2909
- selectedValue: value,
2910
- onChange: (e)=>{
2911
- const target = e.target;
2912
- onChange(target.value);
2913
- }
2914
- });
2915
- return renderer_div({
2916
- style: {
2917
- display: 'flex',
2918
- alignItems: 'center',
2919
- justifyContent: 'space-between',
2920
- gap: '8px',
2921
- marginBottom: '8px'
2922
- },
2923
- children: [
2924
- renderer_div({
2925
- style: {
2926
- fontSize: 'var(--c15t-devtools-font-size-xs)',
2927
- color: 'var(--c15t-devtools-text-muted)',
2928
- minWidth: '60px'
2929
- },
2930
- text: label
2931
- }),
2932
- renderer_div({
2933
- style: {
2934
- flex: '1'
2935
- },
2936
- children: [
2937
- selectField
2938
- ]
2939
- })
2940
- ]
3219
+ selectedValue: value
2941
3220
  });
3221
+ return {
3222
+ element: renderer_div({
3223
+ className: styles_components_module.overrideField,
3224
+ children: [
3225
+ renderer_span({
3226
+ className: styles_components_module.overrideLabel,
3227
+ text: label
3228
+ }),
3229
+ selectField
3230
+ ]
3231
+ }),
3232
+ control: selectField
3233
+ };
3234
+ }
3235
+ function getDraftFromOverrides(overrides) {
3236
+ return {
3237
+ country: overrides?.country ?? '',
3238
+ region: overrides?.region ?? '',
3239
+ language: overrides?.language ?? '',
3240
+ gpc: overrides?.gpc === true ? 'true' : overrides?.gpc === false ? 'false' : ''
3241
+ };
3242
+ }
3243
+ function normalizeOverrideDraft(draft) {
3244
+ return {
3245
+ country: normalizeAlphaCode(draft.country),
3246
+ region: normalizeAlphaCode(draft.region),
3247
+ language: normalizeLanguageCode(draft.language),
3248
+ gpc: 'true' === draft.gpc ? true : 'false' === draft.gpc ? false : void 0
3249
+ };
3250
+ }
3251
+ function normalizeAlphaCode(value) {
3252
+ const normalized = value.trim().toUpperCase();
3253
+ return normalized || void 0;
3254
+ }
3255
+ function normalizeLanguageCode(value) {
3256
+ const normalized = value.trim();
3257
+ return normalized || void 0;
3258
+ }
3259
+ function overridesEqual(a, b) {
3260
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
3261
+ }
3262
+ function hasOverridesValue(overrides) {
3263
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
2942
3264
  }
2943
3265
  const COUNTRY_OPTIONS = [
2944
3266
  {
@@ -3062,6 +3384,32 @@ var __webpack_exports__ = {};
3062
3384
  label: 'ZA - South Africa'
3063
3385
  }
3064
3386
  ];
3387
+ const GPC_OPTIONS = [
3388
+ {
3389
+ value: '',
3390
+ label: '-- Browser Default --'
3391
+ },
3392
+ {
3393
+ value: 'true',
3394
+ label: 'Force On (Simulated)'
3395
+ },
3396
+ {
3397
+ value: 'false',
3398
+ label: 'Force Off (Simulated)'
3399
+ }
3400
+ ];
3401
+ function getEffectiveGpcLabel(gpcOverride) {
3402
+ if (true === gpcOverride) return 'On (Override)';
3403
+ if (false === gpcOverride) return 'Off (Override)';
3404
+ if ('undefined' == typeof window || 'undefined' == typeof navigator) return 'Unknown';
3405
+ try {
3406
+ const nav = navigator;
3407
+ const value = nav.globalPrivacyControl;
3408
+ return true === value || '1' === value ? 'Active' : 'Inactive';
3409
+ } catch {
3410
+ return 'Unknown';
3411
+ }
3412
+ }
3065
3413
  function getModelLabel(model) {
3066
3414
  switch(model){
3067
3415
  case 'opt-in':
@@ -3078,9 +3426,11 @@ var __webpack_exports__ = {};
3078
3426
  return renderer_div({
3079
3427
  className: styles_components_module.gridCard ?? '',
3080
3428
  style: {
3429
+ padding: '6px 8px',
3430
+ minHeight: 'auto',
3081
3431
  flexDirection: 'column',
3082
3432
  alignItems: 'flex-start',
3083
- gap: '2px'
3433
+ gap: '1px'
3084
3434
  },
3085
3435
  children: [
3086
3436
  renderer_span({
@@ -3105,34 +3455,38 @@ var __webpack_exports__ = {};
3105
3455
  function scanDOM(state) {
3106
3456
  const results = [];
3107
3457
  const configuredScripts = state.scripts || [];
3108
- const managedDomains = new Map();
3458
+ const managedResources = [];
3109
3459
  for (const script of configuredScripts)if (script.src) try {
3110
3460
  const url = new URL(script.src, window.location.origin);
3111
- if (url.hostname !== window.location.hostname) managedDomains.set(url.hostname, script.id);
3461
+ if (url.hostname !== window.location.hostname) managedResources.push({
3462
+ scriptId: script.id,
3463
+ domain: url.hostname,
3464
+ pathPrefix: normalizePathname(url.pathname)
3465
+ });
3112
3466
  } catch {}
3113
3467
  const scriptElements = document.querySelectorAll("script[src]");
3114
3468
  for (const el of scriptElements){
3115
3469
  const src = el.getAttribute('src');
3116
3470
  if (!src) continue;
3117
- const resource = checkResource(src, "script", managedDomains);
3471
+ const resource = checkResource(src, "script", managedResources);
3118
3472
  if (resource) results.push(resource);
3119
3473
  }
3120
3474
  const iframeElements = document.querySelectorAll('iframe[src]');
3121
3475
  for (const el of iframeElements){
3122
3476
  const src = el.getAttribute('src');
3123
3477
  if (!src) continue;
3124
- const resource = checkResource(src, 'iframe', managedDomains);
3478
+ const resource = checkResource(src, 'iframe', managedResources);
3125
3479
  if (resource) results.push(resource);
3126
3480
  }
3127
3481
  return results;
3128
3482
  }
3129
- function checkResource(src, type, managedDomains) {
3483
+ function checkResource(src, type, managedResources) {
3130
3484
  try {
3131
3485
  const url = new URL(src, window.location.origin);
3132
3486
  const domain = url.hostname;
3133
3487
  if (domain === window.location.hostname) return null;
3134
3488
  if ('data:' === url.protocol || 'blob:' === url.protocol) return null;
3135
- const managedBy = managedDomains.get(domain);
3489
+ const managedBy = findManagedScriptId(url, managedResources);
3136
3490
  const isManaged = Boolean(managedBy);
3137
3491
  return {
3138
3492
  type,
@@ -3144,6 +3498,21 @@ var __webpack_exports__ = {};
3144
3498
  } catch {}
3145
3499
  return null;
3146
3500
  }
3501
+ function findManagedScriptId(url, managedResources) {
3502
+ const domain = url.hostname;
3503
+ const path = normalizePathname(url.pathname);
3504
+ let bestMatch = null;
3505
+ for (const matcher of managedResources)if (matcher.domain === domain) {
3506
+ if ('/' === matcher.pathPrefix || path.startsWith(matcher.pathPrefix)) {
3507
+ if (!bestMatch || matcher.pathPrefix.length > bestMatch.pathPrefix.length) bestMatch = matcher;
3508
+ }
3509
+ }
3510
+ return bestMatch?.scriptId;
3511
+ }
3512
+ function normalizePathname(pathname) {
3513
+ const trimmed = pathname.trim();
3514
+ return trimmed.length > 0 ? trimmed : '/';
3515
+ }
3147
3516
  function createDomScannerSection(state) {
3148
3517
  let resultsContainer = null;
3149
3518
  let lastScanResults = [];
@@ -3308,7 +3677,7 @@ var __webpack_exports__ = {};
3308
3677
  <polyline points="8 6 2 12 8 18"></polyline>
3309
3678
  </svg>`;
3310
3679
  function scripts_renderScriptsPanel(container, options) {
3311
- const { getState } = options;
3680
+ const { getState, getEvents } = options;
3312
3681
  renderer_clearElement(container);
3313
3682
  const state = getState();
3314
3683
  if (!state) return void container.appendChild(renderer_div({
@@ -3323,6 +3692,7 @@ var __webpack_exports__ = {};
3323
3692
  const scripts = state.scripts || [];
3324
3693
  const loadedScripts = state.loadedScripts || {};
3325
3694
  const networkBlocker = state.networkBlocker;
3695
+ const events = getEvents?.() ?? [];
3326
3696
  if (0 === scripts.length) {
3327
3697
  const scriptsSection = createSection({
3328
3698
  title: 'Configured Scripts',
@@ -3405,6 +3775,20 @@ var __webpack_exports__ = {};
3405
3775
  ]
3406
3776
  });
3407
3777
  container.appendChild(networkSection);
3778
+ const blockedRequestEvents = events.filter((event)=>'network' === event.type);
3779
+ const networkEventsSection = createSection({
3780
+ title: `Blocked Requests (${blockedRequestEvents.length})`,
3781
+ children: 0 === blockedRequestEvents.length ? [
3782
+ renderer_div({
3783
+ style: {
3784
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3785
+ color: 'var(--c15t-devtools-text-muted)'
3786
+ },
3787
+ text: 'No blocked network requests recorded in this session'
3788
+ })
3789
+ ] : createBlockedRequestContent(blockedRequestEvents)
3790
+ });
3791
+ container.appendChild(networkEventsSection);
3408
3792
  const loadedCount = Object.values(loadedScripts).filter(Boolean).length;
3409
3793
  const totalCount = scripts.length;
3410
3794
  const summarySection = createSection({
@@ -3430,12 +3814,127 @@ var __webpack_exports__ = {};
3430
3814
  }
3431
3815
  function checkScriptConsent(state, category) {
3432
3816
  if (!category) return true;
3817
+ if ('function' == typeof state.has) try {
3818
+ return state.has(category);
3819
+ } catch {}
3433
3820
  if ('string' == typeof category) {
3434
3821
  const consents = state.consents || {};
3435
3822
  return true === consents[category];
3436
3823
  }
3437
3824
  return false;
3438
3825
  }
3826
+ function createBlockedRequestContent(events) {
3827
+ const stats = new Map();
3828
+ for (const event of events){
3829
+ const ruleId = getEventRuleId(event) ?? 'unknown';
3830
+ stats.set(ruleId, (stats.get(ruleId) ?? 0) + 1);
3831
+ }
3832
+ const statsList = renderer_div({
3833
+ style: {
3834
+ display: 'flex',
3835
+ flexDirection: 'column',
3836
+ gap: '4px',
3837
+ marginBottom: '8px'
3838
+ },
3839
+ children: [
3840
+ ...stats.entries()
3841
+ ].sort((a, b)=>b[1] - a[1]).map(([ruleId, count])=>createInfoRow({
3842
+ label: 'unknown' === ruleId ? 'Unknown Rule' : `Rule: ${ruleId}`,
3843
+ value: `${count}`
3844
+ }))
3845
+ });
3846
+ const latestEvents = events.slice(0, 5);
3847
+ const latestList = renderer_div({
3848
+ style: {
3849
+ display: 'flex',
3850
+ flexDirection: 'column',
3851
+ gap: '4px'
3852
+ },
3853
+ children: latestEvents.map((event)=>createInfoRow({
3854
+ label: `${formatEventTime(event.timestamp)} ${getEventMethod(event)}`,
3855
+ value: scripts_truncateText(getEventUrl(event), 38)
3856
+ }))
3857
+ });
3858
+ return [
3859
+ statsList,
3860
+ latestList
3861
+ ];
3862
+ }
3863
+ function getEventRuleId(event) {
3864
+ const data = event.data;
3865
+ const rule = data?.rule;
3866
+ const ruleId = rule?.id ?? data?.ruleId;
3867
+ return 'string' == typeof ruleId || 'number' == typeof ruleId ? String(ruleId) : void 0;
3868
+ }
3869
+ function getEventMethod(event) {
3870
+ const data = event.data;
3871
+ const method = data?.method;
3872
+ return 'string' == typeof method ? method.toUpperCase() : 'REQ';
3873
+ }
3874
+ function getEventUrl(event) {
3875
+ const data = event.data;
3876
+ const url = data?.url;
3877
+ return 'string' == typeof url ? url : event.message;
3878
+ }
3879
+ function formatEventTime(timestamp) {
3880
+ return new Date(timestamp).toLocaleTimeString('en-US', {
3881
+ hour12: false,
3882
+ hour: '2-digit',
3883
+ minute: '2-digit',
3884
+ second: '2-digit'
3885
+ });
3886
+ }
3887
+ function scripts_truncateText(text, maxLength) {
3888
+ if (text.length <= maxLength) return text;
3889
+ return `${text.slice(0, maxLength - 3)}...`;
3890
+ }
3891
+ const DEVTOOLS_OVERRIDES_STORAGE_KEY = 'c15t-devtools-overrides';
3892
+ function normalizeStringValue(value) {
3893
+ if ('string' != typeof value) return;
3894
+ const normalized = value.trim();
3895
+ return normalized.length > 0 ? normalized : void 0;
3896
+ }
3897
+ function normalizeBooleanValue(value) {
3898
+ return 'boolean' == typeof value ? value : void 0;
3899
+ }
3900
+ function normalizeOverrides(value) {
3901
+ if (!value || 'object' != typeof value) return null;
3902
+ const source = value;
3903
+ const overrides = {
3904
+ country: normalizeStringValue(source.country),
3905
+ region: normalizeStringValue(source.region),
3906
+ language: normalizeStringValue(source.language),
3907
+ gpc: normalizeBooleanValue(source.gpc)
3908
+ };
3909
+ return hasPersistedOverrides(overrides) ? overrides : null;
3910
+ }
3911
+ function hasPersistedOverrides(overrides) {
3912
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3913
+ }
3914
+ function override_storage_loadPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3915
+ if ('undefined' == typeof window) return null;
3916
+ try {
3917
+ const stored = localStorage.getItem(storageKey);
3918
+ if (!stored) return null;
3919
+ const parsed = JSON.parse(stored);
3920
+ return normalizeOverrides(parsed);
3921
+ } catch {
3922
+ return null;
3923
+ }
3924
+ }
3925
+ function override_storage_persistOverrides(overrides, storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3926
+ if ('undefined' == typeof window) return;
3927
+ try {
3928
+ if (!hasPersistedOverrides(overrides)) return void localStorage.removeItem(storageKey);
3929
+ localStorage.setItem(storageKey, JSON.stringify(overrides));
3930
+ } catch {}
3931
+ }
3932
+ function override_storage_clearPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
3933
+ if ('undefined' == typeof window) return;
3934
+ try {
3935
+ localStorage.removeItem(storageKey);
3936
+ } catch {}
3937
+ }
3439
3938
  const STORAGE_KEYS = {
3440
3939
  C15T: 'c15t',
3441
3940
  PENDING_SYNC: 'c15t:pending-consent-sync',
@@ -3573,12 +4072,37 @@ var __webpack_exports__ = {};
3573
4072
  const { namespace = 'c15tStore', onConnect, onStateChange, onDisconnect } = options;
3574
4073
  let store = null;
3575
4074
  let unsubscribe = null;
3576
- let pollInterval = null;
4075
+ let reconnectTimeout = null;
4076
+ let reconnectAttempts = 0;
4077
+ let hasNotifiedDisconnect = false;
3577
4078
  const listeners = new Set();
4079
+ const INITIAL_RETRY_DELAY_MS = 100;
4080
+ const MAX_RETRY_DELAY_MS = 2000;
4081
+ const DISCONNECT_NOTIFY_ATTEMPTS = 5;
4082
+ function clearReconnectTimer() {
4083
+ if (reconnectTimeout) {
4084
+ clearTimeout(reconnectTimeout);
4085
+ reconnectTimeout = null;
4086
+ }
4087
+ }
4088
+ function resetReconnectState() {
4089
+ reconnectAttempts = 0;
4090
+ hasNotifiedDisconnect = false;
4091
+ }
4092
+ function notifyDisconnectedOnce() {
4093
+ if (hasNotifiedDisconnect) return;
4094
+ hasNotifiedDisconnect = true;
4095
+ onDisconnect?.();
4096
+ }
3578
4097
  function tryConnect() {
3579
4098
  if ('undefined' == typeof window) return false;
3580
4099
  const storeInstance = window[namespace];
3581
4100
  if (storeInstance && 'function' == typeof storeInstance.getState) {
4101
+ if (store === storeInstance && unsubscribe) return true;
4102
+ if (unsubscribe) {
4103
+ unsubscribe();
4104
+ unsubscribe = null;
4105
+ }
3582
4106
  store = storeInstance;
3583
4107
  unsubscribe = store.subscribe((state)=>{
3584
4108
  onStateChange?.(state);
@@ -3586,30 +4110,26 @@ var __webpack_exports__ = {};
3586
4110
  });
3587
4111
  const currentState = store.getState();
3588
4112
  onConnect?.(currentState, store);
3589
- if (pollInterval) {
3590
- clearInterval(pollInterval);
3591
- pollInterval = null;
3592
- }
4113
+ clearReconnectTimer();
4114
+ resetReconnectState();
3593
4115
  return true;
3594
4116
  }
3595
4117
  return false;
3596
4118
  }
4119
+ function scheduleReconnect(immediate = false) {
4120
+ if (store || reconnectTimeout) return;
4121
+ const delay = immediate ? 0 : Math.min(INITIAL_RETRY_DELAY_MS * 2 ** Math.min(reconnectAttempts, 5), MAX_RETRY_DELAY_MS);
4122
+ reconnectTimeout = setTimeout(()=>{
4123
+ reconnectTimeout = null;
4124
+ reconnectAttempts++;
4125
+ if (tryConnect()) return;
4126
+ if (reconnectAttempts >= DISCONNECT_NOTIFY_ATTEMPTS) notifyDisconnectedOnce();
4127
+ scheduleReconnect();
4128
+ }, delay);
4129
+ }
3597
4130
  function startPolling() {
3598
- if (pollInterval) return;
3599
4131
  if (tryConnect()) return;
3600
- let attempts = 0;
3601
- const maxAttempts = 50;
3602
- pollInterval = setInterval(()=>{
3603
- attempts++;
3604
- if (tryConnect()) return;
3605
- if (attempts >= maxAttempts) {
3606
- if (pollInterval) {
3607
- clearInterval(pollInterval);
3608
- pollInterval = null;
3609
- }
3610
- onDisconnect?.();
3611
- }
3612
- }, 100);
4132
+ scheduleReconnect(true);
3613
4133
  }
3614
4134
  startPolling();
3615
4135
  return {
@@ -3623,11 +4143,13 @@ var __webpack_exports__ = {};
3623
4143
  listeners.delete(listener);
3624
4144
  };
3625
4145
  },
4146
+ retryConnection: ()=>{
4147
+ if (store) return;
4148
+ resetReconnectState();
4149
+ scheduleReconnect(true);
4150
+ },
3626
4151
  destroy: ()=>{
3627
- if (pollInterval) {
3628
- clearInterval(pollInterval);
3629
- pollInterval = null;
3630
- }
4152
+ clearReconnectTimer();
3631
4153
  if (unsubscribe) {
3632
4154
  unsubscribe();
3633
4155
  unsubscribe = null;
@@ -3646,14 +4168,61 @@ var __webpack_exports__ = {};
3646
4168
  tokens_options.insertStyleElement = insertStyleElement_default();
3647
4169
  injectStylesIntoStyleTag_default()(tokens.A, tokens_options);
3648
4170
  tokens.A && tokens.A.locals && tokens.A.locals;
4171
+ function normalizeOverridesForPersistence(overrides) {
4172
+ return {
4173
+ country: overrides?.country?.trim() || void 0,
4174
+ region: overrides?.region?.trim() || void 0,
4175
+ language: overrides?.language?.trim() || void 0,
4176
+ gpc: overrides?.gpc
4177
+ };
4178
+ }
4179
+ function persistedOverridesEqual(a, b) {
4180
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
4181
+ }
4182
+ function getBlockedRequestMessage(payload) {
4183
+ const data = payload;
4184
+ const method = 'string' == typeof data?.method ? data.method.toUpperCase() : 'REQUEST';
4185
+ const url = 'string' == typeof data?.url ? data.url : 'unknown-url';
4186
+ return `Network blocked: ${method} ${url}`;
4187
+ }
3649
4188
  function createDevToolsPanel(options) {
3650
4189
  const { namespace = 'c15tStore' } = options;
4190
+ let originalEmbeddedNetworkBlockedCallback;
4191
+ let hasWrappedEmbeddedNetworkBlocker = false;
3651
4192
  const stateManager = state_manager_createStateManager({
3652
4193
  isOpen: true
3653
4194
  });
3654
4195
  const storeConnector = store_connector_createStoreConnector({
3655
4196
  namespace,
3656
- onConnect: ()=>stateManager.setConnected(true),
4197
+ onConnect: (state, store)=>{
4198
+ stateManager.setConnected(true);
4199
+ const currentNetworkBlocker = state.networkBlocker;
4200
+ if (currentNetworkBlocker && !hasWrappedEmbeddedNetworkBlocker) {
4201
+ originalEmbeddedNetworkBlockedCallback = currentNetworkBlocker.onRequestBlocked;
4202
+ hasWrappedEmbeddedNetworkBlocker = true;
4203
+ store.getState().setNetworkBlocker({
4204
+ ...currentNetworkBlocker,
4205
+ onRequestBlocked: (payload)=>{
4206
+ stateManager.addEvent({
4207
+ type: 'network',
4208
+ message: getBlockedRequestMessage(payload),
4209
+ data: payload
4210
+ });
4211
+ if ('function' == typeof originalEmbeddedNetworkBlockedCallback) originalEmbeddedNetworkBlockedCallback(payload);
4212
+ }
4213
+ });
4214
+ }
4215
+ const persistedOverrides = override_storage_loadPersistedOverrides();
4216
+ if (persistedOverrides) {
4217
+ const currentOverrides = normalizeOverridesForPersistence(state.overrides);
4218
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
4219
+ country: persistedOverrides.country,
4220
+ region: persistedOverrides.region,
4221
+ language: persistedOverrides.language,
4222
+ gpc: persistedOverrides.gpc
4223
+ });
4224
+ }
4225
+ },
3657
4226
  onDisconnect: ()=>stateManager.setConnected(false)
3658
4227
  });
3659
4228
  const container = renderer_div({
@@ -3702,29 +4271,61 @@ var __webpack_exports__ = {};
3702
4271
  case 'location':
3703
4272
  location_renderLocationPanel(contentArea, {
3704
4273
  getState: getStoreState,
3705
- onSetOverrides: async (overrides)=>{
4274
+ onApplyOverrides: async (overrides)=>{
3706
4275
  const store = storeConnector.getStore();
3707
4276
  if (store) {
3708
- const current = store.getState().overrides || {};
3709
4277
  await store.getState().setOverrides({
3710
- ...current,
3711
- ...overrides
4278
+ country: overrides.country,
4279
+ region: overrides.region,
4280
+ language: overrides.language,
4281
+ gpc: overrides.gpc
4282
+ });
4283
+ override_storage_persistOverrides({
4284
+ country: overrides.country,
4285
+ region: overrides.region,
4286
+ language: overrides.language,
4287
+ gpc: overrides.gpc
3712
4288
  });
3713
4289
  }
3714
4290
  },
3715
4291
  onClearOverrides: async ()=>{
3716
- await storeConnector.getStore()?.getState().setOverrides(void 0);
4292
+ await storeConnector.getStore()?.getState().setOverrides({
4293
+ country: void 0,
4294
+ region: void 0,
4295
+ language: void 0,
4296
+ gpc: void 0
4297
+ });
4298
+ override_storage_clearPersistedOverrides();
3717
4299
  }
3718
4300
  });
3719
4301
  break;
3720
4302
  case "scripts":
3721
4303
  scripts_renderScriptsPanel(contentArea, {
3722
- getState: getStoreState
4304
+ getState: getStoreState,
4305
+ getEvents: ()=>stateManager.getState().eventLog
3723
4306
  });
3724
4307
  break;
3725
4308
  case 'iab':
3726
4309
  iab_renderIabPanel(contentArea, {
3727
4310
  getState: getStoreState,
4311
+ onSetPurposeConsent: (purposeId, value)=>{
4312
+ storeConnector.getStore()?.getState().iab?.setPurposeConsent(purposeId, value);
4313
+ },
4314
+ onSetVendorConsent: (vendorId, value)=>{
4315
+ storeConnector.getStore()?.getState().iab?.setVendorConsent(vendorId, value);
4316
+ },
4317
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
4318
+ storeConnector.getStore()?.getState().iab?.setSpecialFeatureOptIn(featureId, value);
4319
+ },
4320
+ onAcceptAll: ()=>{
4321
+ storeConnector.getStore()?.getState().iab?.acceptAll();
4322
+ },
4323
+ onRejectAll: ()=>{
4324
+ storeConnector.getStore()?.getState().iab?.rejectAll();
4325
+ },
4326
+ onSave: ()=>{
4327
+ storeConnector.getStore()?.getState().iab?.save();
4328
+ },
3728
4329
  onReset: async ()=>{
3729
4330
  const store = storeConnector.getStore();
3730
4331
  if (store) await reset_consents_resetAllConsents(store);
@@ -3785,6 +4386,14 @@ var __webpack_exports__ = {};
3785
4386
  return {
3786
4387
  element: container,
3787
4388
  destroy: ()=>{
4389
+ const store = storeConnector.getStore();
4390
+ if (store && hasWrappedEmbeddedNetworkBlocker) {
4391
+ const currentNetworkBlocker = store.getState().networkBlocker;
4392
+ if (currentNetworkBlocker) store.getState().setNetworkBlocker({
4393
+ ...currentNetworkBlocker,
4394
+ onRequestBlocked: originalEmbeddedNetworkBlockedCallback
4395
+ });
4396
+ }
3788
4397
  unsubscribe();
3789
4398
  tabsInstance.destroy();
3790
4399
  storeConnector.destroy();