@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/index.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
 
@@ -1864,9 +1933,9 @@ function setPreferenceTriggerVisibility(visible) {
1864
1933
  const elements = getPreferenceTriggerElements();
1865
1934
  for (const el of elements)el.style.display = visible ? '' : 'none';
1866
1935
  }
1867
- function getPreferenceCenterOpener() {
1936
+ function getPreferenceCenterOpener(namespace = 'c15tStore') {
1868
1937
  const win = window;
1869
- const store = win.c15tStore;
1938
+ const store = win[namespace];
1870
1939
  if (store && 'function' == typeof store.getState) {
1871
1940
  const state = store.getState();
1872
1941
  if ('function' == typeof state.setActiveUI) return ()=>{
@@ -1875,6 +1944,7 @@ function getPreferenceCenterOpener() {
1875
1944
  }
1876
1945
  return null;
1877
1946
  }
1947
+ const version = '2.0.0-rc.3';
1878
1948
  const DEVTOOLS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 445" aria-label="c15t">
1879
1949
  <path fill="currentColor" d="M223.178.313c39.064 0 70.732 31.668 70.732 70.732-.001 39.064-31.668 70.731-70.732 70.731-12.181 0-23.642-3.079-33.649-8.502l-55.689 55.689a70.267 70.267 0 0 1 5.574 13.441h167.531c8.695-29.217 35.762-50.523 67.804-50.523 39.064 0 70.731 31.668 70.731 70.732s-31.668 70.732-70.731 70.732c-32.042 0-59.108-21.306-67.803-50.523H139.413a70.417 70.417 0 0 1-7.888 17.396l54.046 54.046c10.893-6.851 23.786-10.815 37.605-10.815 39.064 0 70.732 31.669 70.732 70.733 0 39.064-31.668 70.731-70.732 70.731s-70.732-31.667-70.732-70.731c0-10.518 2.296-20.499 6.414-29.471l-57.78-57.78c-8.972 4.117-18.952 6.414-29.47 6.414-39.063 0-70.731-31.668-70.732-70.732 0-39.064 31.669-70.732 70.733-70.732 12.18 0 23.642 3.079 33.649 8.502l55.688-55.688c-5.423-10.007-8.502-21.469-8.502-33.65 0-39.064 31.668-70.733 70.732-70.733Zm0 343.555c-16.742 0-30.314 13.572-30.314 30.314 0 16.741 13.572 30.313 30.314 30.313s30.314-13.572 30.314-30.313c0-16.742-13.572-30.314-30.314-30.314ZM71.611 192.299c-16.742 0-30.315 13.572-30.315 30.314s13.573 30.314 30.315 30.314c16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314Zm303.138 0c-16.729 0-30.294 13.551-30.315 30.275l.001.039-.001.038c.021 16.725 13.586 30.276 30.315 30.276 16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314ZM223.178 40.73c-16.742 0-30.314 13.573-30.314 30.315s13.573 30.313 30.314 30.313c16.742 0 30.313-13.572 30.314-30.313 0-16.742-13.572-30.314-30.314-30.315Z"/>
1880
1950
  </svg>`;
@@ -2078,7 +2148,7 @@ function getPositionClass(position) {
2078
2148
  }
2079
2149
  }
2080
2150
  function createPanel(options) {
2081
- const { stateManager, storeConnector, onRenderContent, enableUnifiedMode = true } = options;
2151
+ const { stateManager, storeConnector, onRenderContent, namespace = 'c15tStore', enableUnifiedMode = true } = options;
2082
2152
  let removePortal = null;
2083
2153
  let isAnimatingOut = false;
2084
2154
  let draggable = null;
@@ -2107,7 +2177,7 @@ function createPanel(options) {
2107
2177
  return;
2108
2178
  }
2109
2179
  hasPreferenceTrigger = detectPreferenceTrigger();
2110
- const preferenceCenterOpener = getPreferenceCenterOpener();
2180
+ const preferenceCenterOpener = getPreferenceCenterOpener(namespace);
2111
2181
  useUnifiedMode = hasPreferenceTrigger && null !== preferenceCenterOpener;
2112
2182
  if (useUnifiedMode && !dropdownMenu) {
2113
2183
  dropdownMenu = createDropdownMenu({
@@ -2127,7 +2197,7 @@ function createPanel(options) {
2127
2197
  description: 'Open privacy settings',
2128
2198
  icon: PREFERENCES_ICON,
2129
2199
  onClick: ()=>{
2130
- const opener = getPreferenceCenterOpener();
2200
+ const opener = getPreferenceCenterOpener(namespace);
2131
2201
  if (opener) opener();
2132
2202
  }
2133
2203
  },
@@ -2183,6 +2253,7 @@ function createPanel(options) {
2183
2253
  let panelElement = null;
2184
2254
  let backdropElement = null;
2185
2255
  let contentContainer = null;
2256
+ let footerElement = null;
2186
2257
  function createPanelElement() {
2187
2258
  const corner = draggable?.getCorner() ?? stateManager.getState().position;
2188
2259
  const positionClass = getPositionClass(corner);
@@ -2234,33 +2305,53 @@ function createPanel(options) {
2234
2305
  contentContainer = renderer_div({
2235
2306
  className: styles_panel_module.content
2236
2307
  });
2237
- const isConnected = storeConnector.isConnected();
2238
- const footer = renderer_div({
2239
- className: styles_panel_module.footer,
2240
- children: [
2241
- renderer_div({
2242
- className: styles_panel_module.footerStatus,
2243
- children: [
2244
- span({
2245
- className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2246
- }),
2247
- span({
2248
- text: isConnected ? 'Connected' : 'Disconnected'
2249
- })
2250
- ]
2251
- }),
2252
- span({
2253
- text: 'v1.8.3'
2254
- })
2255
- ]
2308
+ footerElement = renderer_div({
2309
+ className: styles_panel_module.footer
2256
2310
  });
2311
+ updateFooter();
2257
2312
  panel.appendChild(header);
2258
2313
  panel.appendChild(contentContainer);
2259
- panel.appendChild(footer);
2260
- if (isConnected) onRenderContent(contentContainer);
2314
+ panel.appendChild(footerElement);
2315
+ if (storeConnector.isConnected()) onRenderContent(contentContainer);
2261
2316
  else renderErrorState(contentContainer);
2262
2317
  return panel;
2263
2318
  }
2319
+ function updateFooter() {
2320
+ if (!footerElement) return;
2321
+ clearElement(footerElement);
2322
+ const isConnected = storeConnector.isConnected();
2323
+ const storeState = storeConnector.getState();
2324
+ const isLoading = storeState?.isLoadingConsentInfo ?? false;
2325
+ const statusChildren = [
2326
+ span({
2327
+ className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2328
+ }),
2329
+ span({
2330
+ text: isConnected ? 'Connected' : 'Disconnected'
2331
+ })
2332
+ ];
2333
+ if (isLoading) statusChildren.push(span({
2334
+ style: {
2335
+ marginLeft: '4px',
2336
+ opacity: '0.7'
2337
+ },
2338
+ text: '\u00b7 Fetching /init\u2026'
2339
+ }));
2340
+ footerElement.appendChild(renderer_div({
2341
+ className: styles_panel_module.footerStatus,
2342
+ children: statusChildren
2343
+ }));
2344
+ if (!isConnected) footerElement.appendChild(renderer_button({
2345
+ className: styles_panel_module.closeButton,
2346
+ text: 'Retry',
2347
+ onClick: ()=>{
2348
+ storeConnector.retryConnection();
2349
+ }
2350
+ }));
2351
+ footerElement.appendChild(span({
2352
+ text: `v${version}`
2353
+ }));
2354
+ }
2264
2355
  function renderErrorState(container) {
2265
2356
  clearElement(container);
2266
2357
  const errorState = renderer_div({
@@ -2283,6 +2374,13 @@ function createPanel(options) {
2283
2374
  renderer_div({
2284
2375
  className: styles_panel_module.errorMessage,
2285
2376
  text: 'c15t consent manager is not initialized. Make sure you have set up the ConsentManagerProvider in your app.'
2377
+ }),
2378
+ renderer_button({
2379
+ className: styles_panel_module.closeButton,
2380
+ text: 'Retry Connection',
2381
+ onClick: ()=>{
2382
+ storeConnector.retryConnection();
2383
+ }
2286
2384
  })
2287
2385
  ]
2288
2386
  });
@@ -2318,6 +2416,7 @@ function createPanel(options) {
2318
2416
  panelElement = null;
2319
2417
  }
2320
2418
  contentContainer = null;
2419
+ footerElement = null;
2321
2420
  isAnimatingOut = false;
2322
2421
  floatingButton.style.display = '';
2323
2422
  stateManager.setOpen(false);
@@ -2336,6 +2435,7 @@ function createPanel(options) {
2336
2435
  update();
2337
2436
  });
2338
2437
  const unsubscribeStore = storeConnector.subscribe(()=>{
2438
+ updateFooter();
2339
2439
  if (contentContainer) if (storeConnector.isConnected()) onRenderContent(contentContainer);
2340
2440
  else renderErrorState(contentContainer);
2341
2441
  });
@@ -2991,13 +3091,13 @@ function renderConsentsPanel(container, options) {
2991
3091
  },
2992
3092
  children: [
2993
3093
  createButton({
2994
- text: 'All',
3094
+ text: 'Accept',
2995
3095
  variant: 'primary',
2996
3096
  small: true,
2997
3097
  onClick: onAcceptAll
2998
3098
  }),
2999
3099
  createButton({
3000
- text: 'None',
3100
+ text: 'Reject',
3001
3101
  variant: 'default',
3002
3102
  small: true,
3003
3103
  onClick: onRejectAll
@@ -3045,16 +3145,22 @@ function renderConsentsPanel(container, options) {
3045
3145
  function formatConsentName(name) {
3046
3146
  return name.replace(/_/g, ' ').replace(/\b\w/g, (l)=>l.toUpperCase());
3047
3147
  }
3148
+ let activeFilter = 'all';
3149
+ let selectedEventId = null;
3048
3150
  function renderEventsPanel(container, options) {
3049
3151
  const { getEvents, onClear } = options;
3050
3152
  clearElement(container);
3051
- const events = getEvents();
3153
+ const allEvents = getEvents();
3154
+ const events = allEvents.filter((event)=>matchesFilter(event, activeFilter));
3155
+ if (!events.some((event)=>event.id === selectedEventId)) selectedEventId = events[0]?.id ?? null;
3156
+ const selectedEvent = events.find((event)=>event.id === selectedEventId) ?? null;
3052
3157
  const header = renderer_div({
3053
3158
  style: {
3054
3159
  display: 'flex',
3055
3160
  alignItems: 'center',
3056
3161
  justifyContent: 'space-between',
3057
- padding: '12px 16px 8px'
3162
+ padding: '12px 16px 8px',
3163
+ gap: '8px'
3058
3164
  },
3059
3165
  children: [
3060
3166
  span({
@@ -3065,44 +3171,130 @@ function renderEventsPanel(container, options) {
3065
3171
  textTransform: 'uppercase',
3066
3172
  letterSpacing: '0.5px'
3067
3173
  },
3068
- text: `Events (${events.length})`
3174
+ text: `Events (${events.length}/${allEvents.length})`
3069
3175
  }),
3070
- createButton({
3071
- text: 'Clear',
3072
- small: true,
3073
- onClick: onClear
3176
+ renderer_div({
3177
+ style: {
3178
+ display: 'flex',
3179
+ gap: '6px'
3180
+ },
3181
+ children: [
3182
+ createButton({
3183
+ text: 'Export',
3184
+ small: true,
3185
+ onClick: ()=>exportEvents(allEvents)
3186
+ }),
3187
+ createButton({
3188
+ text: 'Clear',
3189
+ small: true,
3190
+ onClick: ()=>{
3191
+ onClear();
3192
+ selectedEventId = null;
3193
+ renderEventsPanel(container, options);
3194
+ }
3195
+ })
3196
+ ]
3074
3197
  })
3075
3198
  ]
3076
3199
  });
3077
3200
  container.appendChild(header);
3201
+ container.appendChild(renderer_div({
3202
+ style: {
3203
+ display: 'flex',
3204
+ flexWrap: 'wrap',
3205
+ gap: '6px',
3206
+ padding: '0 16px 8px'
3207
+ },
3208
+ children: EVENT_FILTERS.map((filter)=>createFilterButton(filter, filter === activeFilter, ()=>{
3209
+ activeFilter = filter;
3210
+ selectedEventId = null;
3211
+ renderEventsPanel(container, options);
3212
+ }))
3213
+ }));
3078
3214
  const eventList = renderer_div({
3079
3215
  style: {
3080
3216
  display: 'flex',
3081
3217
  flexDirection: 'column',
3082
3218
  gap: '4px',
3083
3219
  padding: '0 12px 12px',
3084
- maxHeight: '400px',
3220
+ maxHeight: '300px',
3085
3221
  overflowY: 'auto'
3086
3222
  }
3087
3223
  });
3088
- if (0 === events.length) {
3089
- const emptyState = renderer_div({
3090
- style: {
3091
- padding: '32px 16px',
3092
- textAlign: 'center',
3093
- color: 'var(--c15t-text-muted)',
3094
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3095
- },
3096
- text: 'No events recorded yet'
3097
- });
3098
- eventList.appendChild(emptyState);
3099
- } else for (const event of events){
3100
- const eventItem = createEventItem(event);
3101
- eventList.appendChild(eventItem);
3102
- }
3224
+ if (0 === events.length) eventList.appendChild(renderer_div({
3225
+ style: {
3226
+ padding: '20px 16px',
3227
+ textAlign: 'center',
3228
+ color: 'var(--c15t-text-muted)',
3229
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
3230
+ },
3231
+ text: 'No events match this filter'
3232
+ }));
3233
+ else for (const event of events)eventList.appendChild(createEventItem(event, event.id === selectedEventId, ()=>{
3234
+ selectedEventId = event.id;
3235
+ renderEventsPanel(container, options);
3236
+ }));
3103
3237
  container.appendChild(eventList);
3238
+ container.appendChild(createPayloadSection(selectedEvent));
3239
+ }
3240
+ const EVENT_FILTERS = [
3241
+ 'all',
3242
+ 'error',
3243
+ 'consent',
3244
+ 'network',
3245
+ 'iab'
3246
+ ];
3247
+ function createFilterButton(filter, active, onClick) {
3248
+ return createButton({
3249
+ text: filter.toUpperCase(),
3250
+ small: true,
3251
+ variant: active ? 'primary' : 'default',
3252
+ onClick
3253
+ });
3254
+ }
3255
+ function matchesFilter(event, filter) {
3256
+ if ('all' === filter) return true;
3257
+ if ('error' === filter) return 'error' === event.type;
3258
+ if ('consent' === filter) return 'consent_set' === event.type || 'consent_save' === event.type || 'consent_reset' === event.type;
3259
+ if ('network' === filter) return 'network' === event.type;
3260
+ return 'iab' === event.type;
3104
3261
  }
3105
- function createEventItem(event) {
3262
+ function createPayloadSection(event) {
3263
+ const payload = event?.data ? JSON.stringify(event.data, null, 2) : null;
3264
+ return renderer_div({
3265
+ style: {
3266
+ padding: '0 12px 12px'
3267
+ },
3268
+ children: [
3269
+ renderer_div({
3270
+ style: {
3271
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3272
+ fontWeight: '600',
3273
+ color: 'var(--c15t-text-muted)',
3274
+ textTransform: 'uppercase',
3275
+ letterSpacing: '0.5px',
3276
+ marginBottom: '6px'
3277
+ },
3278
+ text: 'Payload'
3279
+ }),
3280
+ renderer_div({
3281
+ className: styles_components_module.gridCard ?? '',
3282
+ style: {
3283
+ padding: '8px',
3284
+ fontFamily: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
3285
+ fontSize: '11px',
3286
+ color: 'var(--c15t-text-muted)',
3287
+ maxHeight: '140px',
3288
+ overflowY: 'auto',
3289
+ whiteSpace: 'pre-wrap',
3290
+ wordBreak: 'break-word'
3291
+ },
3292
+ text: payload || 'Select an event with payload data'
3293
+ })
3294
+ ]
3295
+ });
3296
+ }
3297
+ function createEventItem(event, selected, onSelect) {
3106
3298
  const time = formatTime(event.timestamp);
3107
3299
  const icon = getEventIcon(event.type);
3108
3300
  const color = getEventColor(event.type);
@@ -3113,8 +3305,11 @@ function createEventItem(event) {
3113
3305
  alignItems: 'center',
3114
3306
  gap: '8px',
3115
3307
  padding: '6px 10px',
3116
- fontSize: 'var(--c15t-devtools-font-size-xs)'
3308
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3309
+ cursor: 'pointer',
3310
+ borderColor: selected ? 'var(--c15t-devtools-badge-info, #3b82f6)' : 'var(--c15t-border)'
3117
3311
  },
3312
+ onClick: onSelect,
3118
3313
  children: [
3119
3314
  span({
3120
3315
  style: {
@@ -3143,6 +3338,21 @@ function createEventItem(event) {
3143
3338
  ]
3144
3339
  });
3145
3340
  }
3341
+ function exportEvents(events) {
3342
+ const json = JSON.stringify(events, null, 2);
3343
+ const blob = new Blob([
3344
+ json
3345
+ ], {
3346
+ type: 'application/json'
3347
+ });
3348
+ const url = URL.createObjectURL(blob);
3349
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3350
+ const a = document.createElement('a');
3351
+ a.href = url;
3352
+ a.download = `c15t-events-${timestamp}.json`;
3353
+ a.click();
3354
+ URL.revokeObjectURL(url);
3355
+ }
3146
3356
  function formatTime(timestamp) {
3147
3357
  const date = new Date(timestamp);
3148
3358
  return date.toLocaleTimeString('en-US', {
@@ -3161,7 +3371,10 @@ function getEventIcon(type) {
3161
3371
  return '○';
3162
3372
  case 'error':
3163
3373
  return '✕';
3164
- case 'info':
3374
+ case 'network':
3375
+ return '◉';
3376
+ case 'iab':
3377
+ return '◆';
3165
3378
  default:
3166
3379
  return '○';
3167
3380
  }
@@ -3175,13 +3388,16 @@ function getEventColor(type) {
3175
3388
  return 'var(--c15t-devtools-badge-warning, #f59e0b)';
3176
3389
  case 'error':
3177
3390
  return 'var(--c15t-devtools-badge-error, #ef4444)';
3178
- case 'info':
3391
+ case 'network':
3392
+ return 'var(--c15t-devtools-badge-warning, #f59e0b)';
3393
+ case 'iab':
3394
+ return 'var(--c15t-devtools-badge-info, #3b82f6)';
3179
3395
  default:
3180
3396
  return 'var(--c15t-text-muted)';
3181
3397
  }
3182
3398
  }
3183
3399
  function renderIabPanel(container, options) {
3184
- const { getState, onReset } = options;
3400
+ const { getState, onSetPurposeConsent, onSetVendorConsent, onSetSpecialFeatureOptIn, onAcceptAll, onRejectAll, onSave, onReset } = options;
3185
3401
  clearElement(container);
3186
3402
  const state = getState();
3187
3403
  if (!state) return void container.appendChild(renderer_div({
@@ -3259,7 +3475,9 @@ function renderIabPanel(container, options) {
3259
3475
  for (const [purposeId, consent] of purposeEntries){
3260
3476
  const purposeInfo = purposes[purposeId];
3261
3477
  const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
3262
- purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent)));
3478
+ purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent), (value)=>{
3479
+ onSetPurposeConsent(Number(purposeId), value);
3480
+ }));
3263
3481
  }
3264
3482
  const purposesSection = createSection({
3265
3483
  title: `Purposes (${purposeEntries.length})`,
@@ -3285,7 +3503,9 @@ function renderIabPanel(container, options) {
3285
3503
  for (const [featureId, optIn] of specialFeatureEntries){
3286
3504
  const featureInfo = specialFeatures[featureId];
3287
3505
  const featureName = featureInfo?.name || `Special Feature ${featureId}`;
3288
- specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn)));
3506
+ specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn), (value)=>{
3507
+ onSetSpecialFeatureOptIn(Number(featureId), value);
3508
+ }, 'feature'));
3289
3509
  }
3290
3510
  const specialFeaturesSection = createSection({
3291
3511
  title: `Special Features (${specialFeatureEntries.length})`,
@@ -3325,7 +3545,9 @@ function renderIabPanel(container, options) {
3325
3545
  overflowY: 'auto'
3326
3546
  }
3327
3547
  });
3328
- for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab'));
3548
+ for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab', (value)=>{
3549
+ onSetVendorConsent(Number(vendorId), value);
3550
+ }));
3329
3551
  const vendorsSection = createSection({
3330
3552
  title: `IAB Vendors (${iabVendors.length})`,
3331
3553
  children: [
@@ -3344,7 +3566,9 @@ function renderIabPanel(container, options) {
3344
3566
  overflowY: 'auto'
3345
3567
  }
3346
3568
  });
3347
- for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom'));
3569
+ for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom', (value)=>{
3570
+ onSetVendorConsent(vendorId, value);
3571
+ }));
3348
3572
  const customVendorsSection = createSection({
3349
3573
  title: `Custom Vendors (${customVendors.length})`,
3350
3574
  children: [
@@ -3366,15 +3590,40 @@ function renderIabPanel(container, options) {
3366
3590
  style: {
3367
3591
  display: 'flex',
3368
3592
  alignItems: 'center',
3369
- justifyContent: 'flex-end',
3593
+ justifyContent: 'space-between',
3370
3594
  padding: '12px 16px',
3371
3595
  marginTop: 'auto',
3372
3596
  borderTop: '1px solid var(--c15t-border)',
3373
3597
  backgroundColor: 'var(--c15t-surface)'
3374
3598
  },
3375
3599
  children: [
3600
+ renderer_div({
3601
+ style: {
3602
+ display: 'flex',
3603
+ gap: '6px'
3604
+ },
3605
+ children: [
3606
+ createButton({
3607
+ text: 'Accept All',
3608
+ variant: 'primary',
3609
+ small: true,
3610
+ onClick: onAcceptAll
3611
+ }),
3612
+ createButton({
3613
+ text: 'Reject All',
3614
+ small: true,
3615
+ onClick: onRejectAll
3616
+ }),
3617
+ createButton({
3618
+ text: 'Save',
3619
+ variant: 'primary',
3620
+ small: true,
3621
+ onClick: onSave
3622
+ })
3623
+ ]
3624
+ }),
3376
3625
  createButton({
3377
- text: 'Reset All',
3626
+ text: 'Reset',
3378
3627
  variant: 'danger',
3379
3628
  small: true,
3380
3629
  onClick: onReset
@@ -3383,7 +3632,7 @@ function renderIabPanel(container, options) {
3383
3632
  });
3384
3633
  container.appendChild(footer);
3385
3634
  }
3386
- function createPurposeRow(id, name, consent) {
3635
+ function createPurposeRow(id, name, consent, onChange, ariaKind = 'purpose') {
3387
3636
  return renderer_div({
3388
3637
  style: {
3389
3638
  display: 'flex',
@@ -3406,14 +3655,28 @@ function createPurposeRow(id, name, consent) {
3406
3655
  text: `${id}. ${name}`,
3407
3656
  title: name
3408
3657
  }),
3409
- createBadge({
3410
- text: consent ? '✓' : '✕',
3411
- variant: consent ? 'success' : 'error'
3658
+ renderer_div({
3659
+ style: {
3660
+ display: 'flex',
3661
+ alignItems: 'center',
3662
+ gap: '6px'
3663
+ },
3664
+ children: [
3665
+ createBadge({
3666
+ text: consent ? '✓' : '✕',
3667
+ variant: consent ? 'success' : 'error'
3668
+ }),
3669
+ createToggle({
3670
+ checked: consent,
3671
+ onChange,
3672
+ ariaLabel: `Toggle ${ariaKind} ${id}`
3673
+ })
3674
+ ]
3412
3675
  })
3413
3676
  ]
3414
3677
  });
3415
3678
  }
3416
- function createVendorRow(id, name, consent, type) {
3679
+ function createVendorRow(id, name, consent, type, onChange) {
3417
3680
  return renderer_div({
3418
3681
  style: {
3419
3682
  display: 'flex',
@@ -3460,16 +3723,21 @@ function createVendorRow(id, name, consent, type) {
3460
3723
  createBadge({
3461
3724
  text: consent ? '✓' : '✕',
3462
3725
  variant: consent ? 'success' : 'error'
3726
+ }),
3727
+ createToggle({
3728
+ checked: consent,
3729
+ onChange,
3730
+ ariaLabel: `Toggle vendor ${id}`
3463
3731
  })
3464
3732
  ]
3465
3733
  });
3466
3734
  }
3467
3735
  function truncateText(text, maxLength) {
3468
3736
  if (text.length <= maxLength) return text;
3469
- return text.slice(0, maxLength - 3) + '...';
3737
+ return `${text.slice(0, maxLength - 3)}...`;
3470
3738
  }
3471
3739
  function renderLocationPanel(container, options) {
3472
- const { getState, onSetOverrides, onClearOverrides } = options;
3740
+ const { getState, onApplyOverrides, onClearOverrides } = options;
3473
3741
  clearElement(container);
3474
3742
  const state = getState();
3475
3743
  if (!state) return void container.appendChild(renderer_div({
@@ -3490,145 +3758,230 @@ function renderLocationPanel(container, options) {
3490
3758
  createCompactInfoCard('Jurisdiction', locationInfo?.jurisdiction || '—'),
3491
3759
  createCompactInfoCard('Language', translationConfig?.defaultLanguage || '—')
3492
3760
  ];
3761
+ gridItems.push(createCompactInfoCard('GPC', getEffectiveGpcLabel(overrides?.gpc)));
3493
3762
  if (state.model) gridItems.push(createCompactInfoCard('Model', getModelLabel(state.model)));
3494
3763
  const locationGrid = createGrid({
3495
- columns: 2,
3764
+ columns: 3,
3496
3765
  children: gridItems
3497
3766
  });
3498
3767
  container.appendChild(locationGrid);
3768
+ const initialDraft = getDraftFromOverrides(overrides);
3769
+ let appliedOverrides = normalizeOverrideDraft(initialDraft);
3770
+ let isSubmitting = false;
3771
+ const countryField = createOverrideSelect({
3772
+ label: 'Country',
3773
+ selectOptions: COUNTRY_OPTIONS,
3774
+ value: initialDraft.country
3775
+ });
3776
+ const regionField = createOverrideInput({
3777
+ label: 'Region',
3778
+ placeholder: 'e.g., CA, NY, BE',
3779
+ value: initialDraft.region
3780
+ });
3781
+ const languageField = createOverrideInput({
3782
+ label: 'Language',
3783
+ placeholder: 'e.g., de, fr, en-US',
3784
+ value: initialDraft.language
3785
+ });
3786
+ const gpcField = createOverrideSelect({
3787
+ label: 'GPC',
3788
+ selectOptions: GPC_OPTIONS,
3789
+ value: initialDraft.gpc
3790
+ });
3791
+ const formStatus = span({
3792
+ className: styles_components_module.overrideStatus,
3793
+ text: 'In sync'
3794
+ });
3795
+ const applyButton = createButton({
3796
+ text: 'Apply',
3797
+ variant: 'primary',
3798
+ small: true,
3799
+ disabled: true,
3800
+ onClick: ()=>{
3801
+ applyDraft();
3802
+ }
3803
+ });
3804
+ const revertButton = createButton({
3805
+ text: 'Revert',
3806
+ small: true,
3807
+ disabled: true,
3808
+ onClick: ()=>{
3809
+ setDraftValues(getDraftFromOverrides(appliedOverrides));
3810
+ updateFormState();
3811
+ }
3812
+ });
3813
+ const clearButton = createButton({
3814
+ text: 'Clear',
3815
+ small: true,
3816
+ onClick: ()=>{
3817
+ clearDraftAndOverrides();
3818
+ }
3819
+ });
3820
+ const overrideFieldsGrid = renderer_div({
3821
+ style: {
3822
+ display: 'grid',
3823
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
3824
+ gap: '8px 10px'
3825
+ },
3826
+ children: [
3827
+ countryField.element,
3828
+ regionField.element,
3829
+ languageField.element,
3830
+ gpcField.element
3831
+ ]
3832
+ });
3499
3833
  const overrideSection = createSection({
3500
3834
  title: 'Override Settings',
3501
- actions: [
3502
- createButton({
3503
- text: 'Clear',
3504
- small: true,
3505
- onClick: onClearOverrides
3506
- })
3507
- ],
3508
3835
  children: [
3509
- createOverrideSelect({
3510
- label: 'Country',
3511
- selectOptions: COUNTRY_OPTIONS,
3512
- value: overrides?.country || '',
3513
- onChange: (value)=>onSetOverrides({
3514
- country: value || void 0
3515
- })
3516
- }),
3517
- createOverrideInput({
3518
- label: 'Region',
3519
- placeholder: 'e.g., CA, NY, BE',
3520
- value: overrides?.region || '',
3521
- onChange: (value)=>onSetOverrides({
3522
- region: value || void 0
3523
- })
3836
+ overrideFieldsGrid,
3837
+ span({
3838
+ className: styles_components_module.overrideHint,
3839
+ text: 'GPC override only affects opt-out or unregulated jurisdictions.'
3524
3840
  }),
3525
- createOverrideInput({
3526
- label: 'Language',
3527
- placeholder: 'e.g., de, fr, en',
3528
- value: overrides?.language || '',
3529
- onChange: (value)=>onSetOverrides({
3530
- language: value || void 0
3531
- })
3841
+ renderer_div({
3842
+ className: styles_components_module.overrideActions,
3843
+ children: [
3844
+ renderer_div({
3845
+ className: styles_components_module.overrideActionButtons,
3846
+ children: [
3847
+ revertButton,
3848
+ applyButton,
3849
+ clearButton
3850
+ ]
3851
+ }),
3852
+ formStatus
3853
+ ]
3532
3854
  })
3533
3855
  ]
3534
3856
  });
3535
3857
  container.appendChild(overrideSection);
3536
- const hasOverrides = overrides && (overrides.country || overrides.region || overrides.language);
3537
- if (hasOverrides) {
3538
- const overrideBanner = renderer_div({
3539
- style: {
3540
- padding: '8px 16px',
3541
- backgroundColor: 'var(--c15t-devtools-badge-info-bg)',
3542
- color: 'var(--c15t-devtools-badge-info)',
3543
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3544
- borderTop: '1px solid var(--c15t-devtools-border)'
3545
- },
3546
- text: 'Overrides are active. This may affect consent behavior.'
3858
+ countryField.control.addEventListener('change', updateFormState);
3859
+ regionField.control.addEventListener('input', updateFormState);
3860
+ languageField.control.addEventListener('input', updateFormState);
3861
+ gpcField.control.addEventListener('change', updateFormState);
3862
+ updateFormState();
3863
+ async function applyDraft() {
3864
+ if (isSubmitting) return;
3865
+ const draftOverrides = getDraftOverrides();
3866
+ if (overridesEqual(draftOverrides, appliedOverrides)) return;
3867
+ isSubmitting = true;
3868
+ updateFormState();
3869
+ try {
3870
+ await onApplyOverrides(draftOverrides);
3871
+ appliedOverrides = draftOverrides;
3872
+ } finally{
3873
+ isSubmitting = false;
3874
+ updateFormState();
3875
+ }
3876
+ }
3877
+ async function clearDraftAndOverrides() {
3878
+ if (isSubmitting) return;
3879
+ isSubmitting = true;
3880
+ updateFormState();
3881
+ try {
3882
+ await onClearOverrides();
3883
+ appliedOverrides = {};
3884
+ setDraftValues(getDraftFromOverrides(void 0));
3885
+ } finally{
3886
+ isSubmitting = false;
3887
+ updateFormState();
3888
+ }
3889
+ }
3890
+ function getDraftOverrides() {
3891
+ return normalizeOverrideDraft({
3892
+ country: countryField.control.value,
3893
+ region: regionField.control.value,
3894
+ language: languageField.control.value,
3895
+ gpc: gpcField.control.value
3547
3896
  });
3548
- container.appendChild(overrideBanner);
3897
+ }
3898
+ function setDraftValues(draft) {
3899
+ countryField.control.value = draft.country;
3900
+ regionField.control.value = draft.region;
3901
+ languageField.control.value = draft.language;
3902
+ gpcField.control.value = draft.gpc;
3903
+ }
3904
+ function updateFormState() {
3905
+ const draftOverrides = getDraftOverrides();
3906
+ const hasDraftChanges = !overridesEqual(draftOverrides, appliedOverrides);
3907
+ applyButton.disabled = !hasDraftChanges || isSubmitting;
3908
+ revertButton.disabled = !hasDraftChanges || isSubmitting;
3909
+ clearButton.disabled = isSubmitting;
3910
+ formStatus.textContent = isSubmitting ? 'Applying...' : hasDraftChanges ? 'Unsaved changes' : hasOverridesValue(appliedOverrides) ? 'Overrides active' : 'No overrides';
3911
+ if (styles_components_module.overrideStatusDirty) formStatus.classList.toggle(styles_components_module.overrideStatusDirty, !isSubmitting && hasDraftChanges);
3549
3912
  }
3550
3913
  }
3551
3914
  function createOverrideInput(options) {
3552
- const { label, placeholder, value, onChange } = options;
3553
- let debounceTimer = null;
3915
+ const { label, placeholder, value } = options;
3554
3916
  const inputField = input({
3555
3917
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3556
3918
  placeholder,
3557
- value,
3558
- onInput: (e)=>{
3559
- const target = e.target;
3560
- if (debounceTimer) clearTimeout(debounceTimer);
3561
- debounceTimer = setTimeout(()=>{
3562
- onChange(target.value);
3563
- }, 500);
3564
- }
3565
- });
3566
- return renderer_div({
3567
- style: {
3568
- display: 'flex',
3569
- alignItems: 'center',
3570
- justifyContent: 'space-between',
3571
- gap: '8px',
3572
- marginBottom: '8px'
3573
- },
3574
- children: [
3575
- renderer_div({
3576
- style: {
3577
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3578
- color: 'var(--c15t-devtools-text-muted)',
3579
- minWidth: '60px'
3580
- },
3581
- text: label
3582
- }),
3583
- renderer_div({
3584
- style: {
3585
- flex: '1'
3586
- },
3587
- children: [
3588
- inputField
3589
- ]
3590
- })
3591
- ]
3919
+ value
3592
3920
  });
3921
+ return {
3922
+ element: renderer_div({
3923
+ className: styles_components_module.overrideField,
3924
+ children: [
3925
+ span({
3926
+ className: styles_components_module.overrideLabel,
3927
+ text: label
3928
+ }),
3929
+ inputField
3930
+ ]
3931
+ }),
3932
+ control: inputField
3933
+ };
3593
3934
  }
3594
3935
  function createOverrideSelect(options) {
3595
- const { label, selectOptions, value, onChange } = options;
3936
+ const { label, selectOptions, value } = options;
3596
3937
  const selectField = renderer_select({
3597
3938
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3598
3939
  options: selectOptions,
3599
- selectedValue: value,
3600
- onChange: (e)=>{
3601
- const target = e.target;
3602
- onChange(target.value);
3603
- }
3604
- });
3605
- return renderer_div({
3606
- style: {
3607
- display: 'flex',
3608
- alignItems: 'center',
3609
- justifyContent: 'space-between',
3610
- gap: '8px',
3611
- marginBottom: '8px'
3612
- },
3613
- children: [
3614
- renderer_div({
3615
- style: {
3616
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3617
- color: 'var(--c15t-devtools-text-muted)',
3618
- minWidth: '60px'
3619
- },
3620
- text: label
3621
- }),
3622
- renderer_div({
3623
- style: {
3624
- flex: '1'
3625
- },
3626
- children: [
3627
- selectField
3628
- ]
3629
- })
3630
- ]
3940
+ selectedValue: value
3631
3941
  });
3942
+ return {
3943
+ element: renderer_div({
3944
+ className: styles_components_module.overrideField,
3945
+ children: [
3946
+ span({
3947
+ className: styles_components_module.overrideLabel,
3948
+ text: label
3949
+ }),
3950
+ selectField
3951
+ ]
3952
+ }),
3953
+ control: selectField
3954
+ };
3955
+ }
3956
+ function getDraftFromOverrides(overrides) {
3957
+ return {
3958
+ country: overrides?.country ?? '',
3959
+ region: overrides?.region ?? '',
3960
+ language: overrides?.language ?? '',
3961
+ gpc: overrides?.gpc === true ? 'true' : overrides?.gpc === false ? 'false' : ''
3962
+ };
3963
+ }
3964
+ function normalizeOverrideDraft(draft) {
3965
+ return {
3966
+ country: normalizeAlphaCode(draft.country),
3967
+ region: normalizeAlphaCode(draft.region),
3968
+ language: normalizeLanguageCode(draft.language),
3969
+ gpc: 'true' === draft.gpc ? true : 'false' === draft.gpc ? false : void 0
3970
+ };
3971
+ }
3972
+ function normalizeAlphaCode(value) {
3973
+ const normalized = value.trim().toUpperCase();
3974
+ return normalized || void 0;
3975
+ }
3976
+ function normalizeLanguageCode(value) {
3977
+ const normalized = value.trim();
3978
+ return normalized || void 0;
3979
+ }
3980
+ function overridesEqual(a, b) {
3981
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
3982
+ }
3983
+ function hasOverridesValue(overrides) {
3984
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3632
3985
  }
3633
3986
  const COUNTRY_OPTIONS = [
3634
3987
  {
@@ -3752,6 +4105,32 @@ const COUNTRY_OPTIONS = [
3752
4105
  label: 'ZA - South Africa'
3753
4106
  }
3754
4107
  ];
4108
+ const GPC_OPTIONS = [
4109
+ {
4110
+ value: '',
4111
+ label: '-- Browser Default --'
4112
+ },
4113
+ {
4114
+ value: 'true',
4115
+ label: 'Force On (Simulated)'
4116
+ },
4117
+ {
4118
+ value: 'false',
4119
+ label: 'Force Off (Simulated)'
4120
+ }
4121
+ ];
4122
+ function getEffectiveGpcLabel(gpcOverride) {
4123
+ if (true === gpcOverride) return 'On (Override)';
4124
+ if (false === gpcOverride) return 'Off (Override)';
4125
+ if ('undefined' == typeof window || 'undefined' == typeof navigator) return 'Unknown';
4126
+ try {
4127
+ const nav = navigator;
4128
+ const value = nav.globalPrivacyControl;
4129
+ return true === value || '1' === value ? 'Active' : 'Inactive';
4130
+ } catch {
4131
+ return 'Unknown';
4132
+ }
4133
+ }
3755
4134
  function getModelLabel(model) {
3756
4135
  switch(model){
3757
4136
  case 'opt-in':
@@ -3768,9 +4147,11 @@ function createCompactInfoCard(label, value) {
3768
4147
  return renderer_div({
3769
4148
  className: styles_components_module.gridCard ?? '',
3770
4149
  style: {
4150
+ padding: '6px 8px',
4151
+ minHeight: 'auto',
3771
4152
  flexDirection: 'column',
3772
4153
  alignItems: 'flex-start',
3773
- gap: '2px'
4154
+ gap: '1px'
3774
4155
  },
3775
4156
  children: [
3776
4157
  span({
@@ -3795,34 +4176,38 @@ const dismissedResources = new Set();
3795
4176
  function scanDOM(state) {
3796
4177
  const results = [];
3797
4178
  const configuredScripts = state.scripts || [];
3798
- const managedDomains = new Map();
4179
+ const managedResources = [];
3799
4180
  for (const script of configuredScripts)if (script.src) try {
3800
4181
  const url = new URL(script.src, window.location.origin);
3801
- if (url.hostname !== window.location.hostname) managedDomains.set(url.hostname, script.id);
4182
+ if (url.hostname !== window.location.hostname) managedResources.push({
4183
+ scriptId: script.id,
4184
+ domain: url.hostname,
4185
+ pathPrefix: normalizePathname(url.pathname)
4186
+ });
3802
4187
  } catch {}
3803
4188
  const scriptElements = document.querySelectorAll("script[src]");
3804
4189
  for (const el of scriptElements){
3805
4190
  const src = el.getAttribute('src');
3806
4191
  if (!src) continue;
3807
- const resource = checkResource(src, "script", managedDomains);
4192
+ const resource = checkResource(src, "script", managedResources);
3808
4193
  if (resource) results.push(resource);
3809
4194
  }
3810
4195
  const iframeElements = document.querySelectorAll('iframe[src]');
3811
4196
  for (const el of iframeElements){
3812
4197
  const src = el.getAttribute('src');
3813
4198
  if (!src) continue;
3814
- const resource = checkResource(src, 'iframe', managedDomains);
4199
+ const resource = checkResource(src, 'iframe', managedResources);
3815
4200
  if (resource) results.push(resource);
3816
4201
  }
3817
4202
  return results;
3818
4203
  }
3819
- function checkResource(src, type, managedDomains) {
4204
+ function checkResource(src, type, managedResources) {
3820
4205
  try {
3821
4206
  const url = new URL(src, window.location.origin);
3822
4207
  const domain = url.hostname;
3823
4208
  if (domain === window.location.hostname) return null;
3824
4209
  if ('data:' === url.protocol || 'blob:' === url.protocol) return null;
3825
- const managedBy = managedDomains.get(domain);
4210
+ const managedBy = findManagedScriptId(url, managedResources);
3826
4211
  const isManaged = Boolean(managedBy);
3827
4212
  return {
3828
4213
  type,
@@ -3834,6 +4219,21 @@ function checkResource(src, type, managedDomains) {
3834
4219
  } catch {}
3835
4220
  return null;
3836
4221
  }
4222
+ function findManagedScriptId(url, managedResources) {
4223
+ const domain = url.hostname;
4224
+ const path = normalizePathname(url.pathname);
4225
+ let bestMatch = null;
4226
+ for (const matcher of managedResources)if (matcher.domain === domain) {
4227
+ if ('/' === matcher.pathPrefix || path.startsWith(matcher.pathPrefix)) {
4228
+ if (!bestMatch || matcher.pathPrefix.length > bestMatch.pathPrefix.length) bestMatch = matcher;
4229
+ }
4230
+ }
4231
+ return bestMatch?.scriptId;
4232
+ }
4233
+ function normalizePathname(pathname) {
4234
+ const trimmed = pathname.trim();
4235
+ return trimmed.length > 0 ? trimmed : '/';
4236
+ }
3837
4237
  function createDomScannerSection(state) {
3838
4238
  let resultsContainer = null;
3839
4239
  let lastScanResults = [];
@@ -3998,7 +4398,7 @@ const CODE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" f
3998
4398
  <polyline points="8 6 2 12 8 18"></polyline>
3999
4399
  </svg>`;
4000
4400
  function renderScriptsPanel(container, options) {
4001
- const { getState } = options;
4401
+ const { getState, getEvents } = options;
4002
4402
  clearElement(container);
4003
4403
  const state = getState();
4004
4404
  if (!state) return void container.appendChild(renderer_div({
@@ -4013,6 +4413,7 @@ function renderScriptsPanel(container, options) {
4013
4413
  const scripts = state.scripts || [];
4014
4414
  const loadedScripts = state.loadedScripts || {};
4015
4415
  const networkBlocker = state.networkBlocker;
4416
+ const events = getEvents?.() ?? [];
4016
4417
  if (0 === scripts.length) {
4017
4418
  const scriptsSection = createSection({
4018
4419
  title: 'Configured Scripts',
@@ -4095,6 +4496,20 @@ function renderScriptsPanel(container, options) {
4095
4496
  ]
4096
4497
  });
4097
4498
  container.appendChild(networkSection);
4499
+ const blockedRequestEvents = events.filter((event)=>'network' === event.type);
4500
+ const networkEventsSection = createSection({
4501
+ title: `Blocked Requests (${blockedRequestEvents.length})`,
4502
+ children: 0 === blockedRequestEvents.length ? [
4503
+ renderer_div({
4504
+ style: {
4505
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
4506
+ color: 'var(--c15t-devtools-text-muted)'
4507
+ },
4508
+ text: 'No blocked network requests recorded in this session'
4509
+ })
4510
+ ] : createBlockedRequestContent(blockedRequestEvents)
4511
+ });
4512
+ container.appendChild(networkEventsSection);
4098
4513
  const loadedCount = Object.values(loadedScripts).filter(Boolean).length;
4099
4514
  const totalCount = scripts.length;
4100
4515
  const summarySection = createSection({
@@ -4120,12 +4535,127 @@ function renderScriptsPanel(container, options) {
4120
4535
  }
4121
4536
  function checkScriptConsent(state, category) {
4122
4537
  if (!category) return true;
4538
+ if ('function' == typeof state.has) try {
4539
+ return state.has(category);
4540
+ } catch {}
4123
4541
  if ('string' == typeof category) {
4124
4542
  const consents = state.consents || {};
4125
4543
  return true === consents[category];
4126
4544
  }
4127
4545
  return false;
4128
4546
  }
4547
+ function createBlockedRequestContent(events) {
4548
+ const stats = new Map();
4549
+ for (const event of events){
4550
+ const ruleId = getEventRuleId(event) ?? 'unknown';
4551
+ stats.set(ruleId, (stats.get(ruleId) ?? 0) + 1);
4552
+ }
4553
+ const statsList = renderer_div({
4554
+ style: {
4555
+ display: 'flex',
4556
+ flexDirection: 'column',
4557
+ gap: '4px',
4558
+ marginBottom: '8px'
4559
+ },
4560
+ children: [
4561
+ ...stats.entries()
4562
+ ].sort((a, b)=>b[1] - a[1]).map(([ruleId, count])=>createInfoRow({
4563
+ label: 'unknown' === ruleId ? 'Unknown Rule' : `Rule: ${ruleId}`,
4564
+ value: `${count}`
4565
+ }))
4566
+ });
4567
+ const latestEvents = events.slice(0, 5);
4568
+ const latestList = renderer_div({
4569
+ style: {
4570
+ display: 'flex',
4571
+ flexDirection: 'column',
4572
+ gap: '4px'
4573
+ },
4574
+ children: latestEvents.map((event)=>createInfoRow({
4575
+ label: `${formatEventTime(event.timestamp)} ${getEventMethod(event)}`,
4576
+ value: scripts_truncateText(getEventUrl(event), 38)
4577
+ }))
4578
+ });
4579
+ return [
4580
+ statsList,
4581
+ latestList
4582
+ ];
4583
+ }
4584
+ function getEventRuleId(event) {
4585
+ const data = event.data;
4586
+ const rule = data?.rule;
4587
+ const ruleId = rule?.id ?? data?.ruleId;
4588
+ return 'string' == typeof ruleId || 'number' == typeof ruleId ? String(ruleId) : void 0;
4589
+ }
4590
+ function getEventMethod(event) {
4591
+ const data = event.data;
4592
+ const method = data?.method;
4593
+ return 'string' == typeof method ? method.toUpperCase() : 'REQ';
4594
+ }
4595
+ function getEventUrl(event) {
4596
+ const data = event.data;
4597
+ const url = data?.url;
4598
+ return 'string' == typeof url ? url : event.message;
4599
+ }
4600
+ function formatEventTime(timestamp) {
4601
+ return new Date(timestamp).toLocaleTimeString('en-US', {
4602
+ hour12: false,
4603
+ hour: '2-digit',
4604
+ minute: '2-digit',
4605
+ second: '2-digit'
4606
+ });
4607
+ }
4608
+ function scripts_truncateText(text, maxLength) {
4609
+ if (text.length <= maxLength) return text;
4610
+ return `${text.slice(0, maxLength - 3)}...`;
4611
+ }
4612
+ const DEVTOOLS_OVERRIDES_STORAGE_KEY = 'c15t-devtools-overrides';
4613
+ function normalizeStringValue(value) {
4614
+ if ('string' != typeof value) return;
4615
+ const normalized = value.trim();
4616
+ return normalized.length > 0 ? normalized : void 0;
4617
+ }
4618
+ function normalizeBooleanValue(value) {
4619
+ return 'boolean' == typeof value ? value : void 0;
4620
+ }
4621
+ function normalizeOverrides(value) {
4622
+ if (!value || 'object' != typeof value) return null;
4623
+ const source = value;
4624
+ const overrides = {
4625
+ country: normalizeStringValue(source.country),
4626
+ region: normalizeStringValue(source.region),
4627
+ language: normalizeStringValue(source.language),
4628
+ gpc: normalizeBooleanValue(source.gpc)
4629
+ };
4630
+ return hasPersistedOverrides(overrides) ? overrides : null;
4631
+ }
4632
+ function hasPersistedOverrides(overrides) {
4633
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
4634
+ }
4635
+ function loadPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4636
+ if ('undefined' == typeof window) return null;
4637
+ try {
4638
+ const stored = localStorage.getItem(storageKey);
4639
+ if (!stored) return null;
4640
+ const parsed = JSON.parse(stored);
4641
+ return normalizeOverrides(parsed);
4642
+ } catch {
4643
+ return null;
4644
+ }
4645
+ }
4646
+ function persistOverrides(overrides, storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4647
+ if ('undefined' == typeof window) return;
4648
+ try {
4649
+ if (!hasPersistedOverrides(overrides)) return void localStorage.removeItem(storageKey);
4650
+ localStorage.setItem(storageKey, JSON.stringify(overrides));
4651
+ } catch {}
4652
+ }
4653
+ function clearPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4654
+ if ('undefined' == typeof window) return;
4655
+ try {
4656
+ localStorage.removeItem(storageKey);
4657
+ } catch {}
4658
+ }
4129
4659
  const STORAGE_KEYS = {
4130
4660
  C15T: 'c15t',
4131
4661
  PENDING_SYNC: 'c15t:pending-consent-sync',
@@ -4263,12 +4793,37 @@ function createStoreConnector(options = {}) {
4263
4793
  const { namespace = 'c15tStore', onConnect, onStateChange, onDisconnect } = options;
4264
4794
  let store = null;
4265
4795
  let unsubscribe = null;
4266
- let pollInterval = null;
4796
+ let reconnectTimeout = null;
4797
+ let reconnectAttempts = 0;
4798
+ let hasNotifiedDisconnect = false;
4267
4799
  const listeners = new Set();
4800
+ const INITIAL_RETRY_DELAY_MS = 100;
4801
+ const MAX_RETRY_DELAY_MS = 2000;
4802
+ const DISCONNECT_NOTIFY_ATTEMPTS = 5;
4803
+ function clearReconnectTimer() {
4804
+ if (reconnectTimeout) {
4805
+ clearTimeout(reconnectTimeout);
4806
+ reconnectTimeout = null;
4807
+ }
4808
+ }
4809
+ function resetReconnectState() {
4810
+ reconnectAttempts = 0;
4811
+ hasNotifiedDisconnect = false;
4812
+ }
4813
+ function notifyDisconnectedOnce() {
4814
+ if (hasNotifiedDisconnect) return;
4815
+ hasNotifiedDisconnect = true;
4816
+ onDisconnect?.();
4817
+ }
4268
4818
  function tryConnect() {
4269
4819
  if ('undefined' == typeof window) return false;
4270
4820
  const storeInstance = window[namespace];
4271
4821
  if (storeInstance && 'function' == typeof storeInstance.getState) {
4822
+ if (store === storeInstance && unsubscribe) return true;
4823
+ if (unsubscribe) {
4824
+ unsubscribe();
4825
+ unsubscribe = null;
4826
+ }
4272
4827
  store = storeInstance;
4273
4828
  unsubscribe = store.subscribe((state)=>{
4274
4829
  onStateChange?.(state);
@@ -4276,30 +4831,26 @@ function createStoreConnector(options = {}) {
4276
4831
  });
4277
4832
  const currentState = store.getState();
4278
4833
  onConnect?.(currentState, store);
4279
- if (pollInterval) {
4280
- clearInterval(pollInterval);
4281
- pollInterval = null;
4282
- }
4834
+ clearReconnectTimer();
4835
+ resetReconnectState();
4283
4836
  return true;
4284
4837
  }
4285
4838
  return false;
4286
4839
  }
4840
+ function scheduleReconnect(immediate = false) {
4841
+ if (store || reconnectTimeout) return;
4842
+ const delay = immediate ? 0 : Math.min(INITIAL_RETRY_DELAY_MS * 2 ** Math.min(reconnectAttempts, 5), MAX_RETRY_DELAY_MS);
4843
+ reconnectTimeout = setTimeout(()=>{
4844
+ reconnectTimeout = null;
4845
+ reconnectAttempts++;
4846
+ if (tryConnect()) return;
4847
+ if (reconnectAttempts >= DISCONNECT_NOTIFY_ATTEMPTS) notifyDisconnectedOnce();
4848
+ scheduleReconnect();
4849
+ }, delay);
4850
+ }
4287
4851
  function startPolling() {
4288
- if (pollInterval) return;
4289
4852
  if (tryConnect()) return;
4290
- let attempts = 0;
4291
- const maxAttempts = 50;
4292
- pollInterval = setInterval(()=>{
4293
- attempts++;
4294
- if (tryConnect()) return;
4295
- if (attempts >= maxAttempts) {
4296
- if (pollInterval) {
4297
- clearInterval(pollInterval);
4298
- pollInterval = null;
4299
- }
4300
- onDisconnect?.();
4301
- }
4302
- }, 100);
4853
+ scheduleReconnect(true);
4303
4854
  }
4304
4855
  startPolling();
4305
4856
  return {
@@ -4313,11 +4864,13 @@ function createStoreConnector(options = {}) {
4313
4864
  listeners.delete(listener);
4314
4865
  };
4315
4866
  },
4867
+ retryConnection: ()=>{
4868
+ if (store) return;
4869
+ resetReconnectState();
4870
+ scheduleReconnect(true);
4871
+ },
4316
4872
  destroy: ()=>{
4317
- if (pollInterval) {
4318
- clearInterval(pollInterval);
4319
- pollInterval = null;
4320
- }
4873
+ clearReconnectTimer();
4321
4874
  if (unsubscribe) {
4322
4875
  unsubscribe();
4323
4876
  unsubscribe = null;
@@ -4345,6 +4898,86 @@ tokens_options.domAPI = styleDomAPI_default();
4345
4898
  tokens_options.insertStyleElement = insertStyleElement_default();
4346
4899
  injectStylesIntoStyleTag_default()(tokens.A, tokens_options);
4347
4900
  tokens.A && tokens.A.locals && tokens.A.locals;
4901
+ const PANEL_HEIGHT_TRANSITION = 'height var(--c15t-duration-normal, 200ms) var(--c15t-easing, cubic-bezier(0.4, 0, 0.2, 1))';
4902
+ const PANEL_HEIGHT_TRANSITION_MS = 200;
4903
+ const PANEL_HEIGHT_TRANSITION_BUFFER_MS = 80;
4904
+ function normalizeOverridesForPersistence(overrides) {
4905
+ return {
4906
+ country: overrides?.country?.trim() || void 0,
4907
+ region: overrides?.region?.trim() || void 0,
4908
+ language: overrides?.language?.trim() || void 0,
4909
+ gpc: overrides?.gpc
4910
+ };
4911
+ }
4912
+ function persistedOverridesEqual(a, b) {
4913
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
4914
+ }
4915
+ function getBlockedRequestMessage(payload) {
4916
+ const data = payload;
4917
+ const method = 'string' == typeof data?.method ? data.method.toUpperCase() : 'REQUEST';
4918
+ const url = 'string' == typeof data?.url ? data.url : 'unknown-url';
4919
+ return `Network blocked: ${method} ${url}`;
4920
+ }
4921
+ function prefersReducedMotion() {
4922
+ return 'undefined' != typeof window && 'function' == typeof window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
4923
+ }
4924
+ function createPanelHeightAnimator() {
4925
+ let activePanel = null;
4926
+ let frameId = null;
4927
+ let timeoutId = null;
4928
+ let removeTransitionListener = null;
4929
+ function clearAnimationState() {
4930
+ if (null !== frameId) {
4931
+ window.cancelAnimationFrame(frameId);
4932
+ frameId = null;
4933
+ }
4934
+ if (null !== timeoutId) {
4935
+ clearTimeout(timeoutId);
4936
+ timeoutId = null;
4937
+ }
4938
+ if (removeTransitionListener) {
4939
+ removeTransitionListener();
4940
+ removeTransitionListener = null;
4941
+ }
4942
+ if (activePanel) {
4943
+ activePanel.style.height = '';
4944
+ activePanel.style.transition = '';
4945
+ activePanel.style.willChange = '';
4946
+ activePanel = null;
4947
+ }
4948
+ }
4949
+ function animate(panel, previousHeight) {
4950
+ if (!Number.isFinite(previousHeight) || prefersReducedMotion()) return;
4951
+ const nextHeight = panel.getBoundingClientRect().height;
4952
+ if (!Number.isFinite(nextHeight) || Math.abs(nextHeight - previousHeight) < 1) return;
4953
+ clearAnimationState();
4954
+ activePanel = panel;
4955
+ panel.style.height = `${previousHeight}px`;
4956
+ panel.style.willChange = 'height';
4957
+ panel.getBoundingClientRect();
4958
+ const handleTransitionEnd = (event)=>{
4959
+ const transitionEvent = event;
4960
+ if ('string' == typeof transitionEvent.propertyName && transitionEvent.propertyName && 'height' !== transitionEvent.propertyName) return;
4961
+ clearAnimationState();
4962
+ };
4963
+ panel.addEventListener('transitionend', handleTransitionEnd);
4964
+ removeTransitionListener = ()=>{
4965
+ panel.removeEventListener('transitionend', handleTransitionEnd);
4966
+ };
4967
+ frameId = window.requestAnimationFrame(()=>{
4968
+ frameId = null;
4969
+ panel.style.transition = PANEL_HEIGHT_TRANSITION;
4970
+ panel.style.height = `${nextHeight}px`;
4971
+ });
4972
+ timeoutId = setTimeout(()=>{
4973
+ clearAnimationState();
4974
+ }, PANEL_HEIGHT_TRANSITION_MS + PANEL_HEIGHT_TRANSITION_BUFFER_MS);
4975
+ }
4976
+ return {
4977
+ animate,
4978
+ destroy: clearAnimationState
4979
+ };
4980
+ }
4348
4981
  function createDevTools(options = {}) {
4349
4982
  const { namespace = 'c15tStore', position = 'bottom-right', defaultOpen = false } = options;
4350
4983
  const stateManager = createStateManager({
@@ -4352,6 +4985,8 @@ function createDevTools(options = {}) {
4352
4985
  isOpen: defaultOpen
4353
4986
  });
4354
4987
  let originalCallbacks = {};
4988
+ let originalNetworkBlockedCallback;
4989
+ let hasWrappedNetworkBlockerCallback = false;
4355
4990
  const storeConnector = createStoreConnector({
4356
4991
  namespace,
4357
4992
  onConnect: (state, store)=>{
@@ -4395,6 +5030,48 @@ function createDevTools(options = {}) {
4395
5030
  });
4396
5031
  if ('function' == typeof originalCallbacks.onBeforeConsentRevocationReload) originalCallbacks.onBeforeConsentRevocationReload(payload);
4397
5032
  });
5033
+ const currentNetworkBlocker = store.getState().networkBlocker;
5034
+ if (currentNetworkBlocker && !hasWrappedNetworkBlockerCallback) {
5035
+ originalNetworkBlockedCallback = currentNetworkBlocker.onRequestBlocked;
5036
+ hasWrappedNetworkBlockerCallback = true;
5037
+ store.getState().setNetworkBlocker({
5038
+ ...currentNetworkBlocker,
5039
+ onRequestBlocked: (payload)=>{
5040
+ stateManager.addEvent({
5041
+ type: 'network',
5042
+ message: getBlockedRequestMessage(payload),
5043
+ data: payload
5044
+ });
5045
+ if ('function' == typeof originalNetworkBlockedCallback) originalNetworkBlockedCallback(payload);
5046
+ }
5047
+ });
5048
+ }
5049
+ const persistedOverrides = loadPersistedOverrides();
5050
+ if (persistedOverrides) {
5051
+ const currentOverrides = normalizeOverridesForPersistence(store.getState().overrides);
5052
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
5053
+ country: persistedOverrides.country,
5054
+ region: persistedOverrides.region,
5055
+ language: persistedOverrides.language,
5056
+ gpc: persistedOverrides.gpc
5057
+ }).then(()=>{
5058
+ stateManager.addEvent({
5059
+ type: 'info',
5060
+ message: 'Applied persisted devtools overrides',
5061
+ data: {
5062
+ country: persistedOverrides.country,
5063
+ region: persistedOverrides.region,
5064
+ language: persistedOverrides.language,
5065
+ gpc: persistedOverrides.gpc
5066
+ }
5067
+ });
5068
+ }).catch(()=>{
5069
+ stateManager.addEvent({
5070
+ type: 'error',
5071
+ message: 'Failed to apply persisted devtools overrides'
5072
+ });
5073
+ });
5074
+ }
4398
5075
  },
4399
5076
  onDisconnect: ()=>{
4400
5077
  stateManager.setConnected(false);
@@ -4406,14 +5083,18 @@ function createDevTools(options = {}) {
4406
5083
  onStateChange: ()=>{}
4407
5084
  });
4408
5085
  let tabsInstance = null;
5086
+ const panelHeightAnimator = createPanelHeightAnimator();
4409
5087
  const panelInstance = createPanel({
4410
5088
  stateManager,
4411
5089
  storeConnector,
5090
+ namespace,
4412
5091
  onRenderContent: (container)=>{
4413
5092
  renderContent(container, stateManager, storeConnector);
4414
5093
  }
4415
5094
  });
4416
5095
  function renderContent(container, stateManager, storeConnector) {
5096
+ const panel = container.parentElement;
5097
+ const previousPanelHeight = panel?.getBoundingClientRect().height ?? 0;
4417
5098
  clearElement(container);
4418
5099
  const storeState = storeConnector.getState();
4419
5100
  const disabledTabs = [];
@@ -4495,38 +5176,46 @@ function createDevTools(options = {}) {
4495
5176
  case 'location':
4496
5177
  renderLocationPanel(panelContent, {
4497
5178
  getState: getStoreState,
4498
- onSetOverrides: async (overrides)=>{
5179
+ onApplyOverrides: async (overrides)=>{
4499
5180
  const store = storeConnector.getStore();
4500
5181
  if (store) {
4501
- const currentOverrides = store.getState().overrides || {};
4502
5182
  await store.getState().setOverrides({
4503
- ...currentOverrides,
4504
- ...overrides
5183
+ country: overrides.country,
5184
+ region: overrides.region,
5185
+ language: overrides.language,
5186
+ gpc: overrides.gpc
4505
5187
  });
4506
- stateManager.addEvent({
4507
- type: 'info',
4508
- message: 'Overrides updated',
4509
- data: overrides
5188
+ persistOverrides({
5189
+ country: overrides.country,
5190
+ region: overrides.region,
5191
+ language: overrides.language,
5192
+ gpc: overrides.gpc
4510
5193
  });
4511
- await store.getState().initConsentManager();
4512
5194
  stateManager.addEvent({
4513
5195
  type: 'info',
4514
- message: 'Consent manager re-initialized with new overrides'
5196
+ message: 'Overrides updated',
5197
+ data: {
5198
+ country: overrides.country,
5199
+ region: overrides.region,
5200
+ language: overrides.language,
5201
+ gpc: overrides.gpc
5202
+ }
4515
5203
  });
4516
5204
  }
4517
5205
  },
4518
5206
  onClearOverrides: async ()=>{
4519
5207
  const store = storeConnector.getStore();
4520
5208
  if (store) {
4521
- await store.getState().setOverrides(void 0);
4522
- stateManager.addEvent({
4523
- type: 'info',
4524
- message: 'Overrides cleared'
5209
+ await store.getState().setOverrides({
5210
+ country: void 0,
5211
+ region: void 0,
5212
+ language: void 0,
5213
+ gpc: void 0
4525
5214
  });
4526
- await store.getState().initConsentManager();
5215
+ clearPersistedOverrides();
4527
5216
  stateManager.addEvent({
4528
5217
  type: 'info',
4529
- message: 'Consent manager re-initialized'
5218
+ message: 'Overrides cleared'
4530
5219
  });
4531
5220
  }
4532
5221
  }
@@ -4534,12 +5223,85 @@ function createDevTools(options = {}) {
4534
5223
  break;
4535
5224
  case "scripts":
4536
5225
  renderScriptsPanel(panelContent, {
4537
- getState: getStoreState
5226
+ getState: getStoreState,
5227
+ getEvents: ()=>stateManager.getState().eventLog
4538
5228
  });
4539
5229
  break;
4540
5230
  case 'iab':
4541
5231
  renderIabPanel(panelContent, {
4542
5232
  getState: getStoreState,
5233
+ onSetPurposeConsent: (purposeId, value)=>{
5234
+ const iab = storeConnector.getStore()?.getState().iab;
5235
+ if (!iab) return;
5236
+ iab.setPurposeConsent(purposeId, value);
5237
+ stateManager.addEvent({
5238
+ type: 'iab',
5239
+ message: `IAB purpose ${purposeId} set to ${value}`,
5240
+ data: {
5241
+ purposeId,
5242
+ value
5243
+ }
5244
+ });
5245
+ },
5246
+ onSetVendorConsent: (vendorId, value)=>{
5247
+ const iab = storeConnector.getStore()?.getState().iab;
5248
+ if (!iab) return;
5249
+ iab.setVendorConsent(vendorId, value);
5250
+ stateManager.addEvent({
5251
+ type: 'iab',
5252
+ message: `IAB vendor ${vendorId} set to ${value}`,
5253
+ data: {
5254
+ vendorId,
5255
+ value
5256
+ }
5257
+ });
5258
+ },
5259
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
5260
+ const iab = storeConnector.getStore()?.getState().iab;
5261
+ if (!iab) return;
5262
+ iab.setSpecialFeatureOptIn(featureId, value);
5263
+ stateManager.addEvent({
5264
+ type: 'iab',
5265
+ message: `IAB feature ${featureId} set to ${value}`,
5266
+ data: {
5267
+ featureId,
5268
+ value
5269
+ }
5270
+ });
5271
+ },
5272
+ onAcceptAll: ()=>{
5273
+ const iab = storeConnector.getStore()?.getState().iab;
5274
+ if (!iab) return;
5275
+ iab.acceptAll();
5276
+ stateManager.addEvent({
5277
+ type: 'iab',
5278
+ message: 'IAB accept all selected'
5279
+ });
5280
+ },
5281
+ onRejectAll: ()=>{
5282
+ const iab = storeConnector.getStore()?.getState().iab;
5283
+ if (!iab) return;
5284
+ iab.rejectAll();
5285
+ stateManager.addEvent({
5286
+ type: 'iab',
5287
+ message: 'IAB reject all selected'
5288
+ });
5289
+ },
5290
+ onSave: ()=>{
5291
+ const iab = storeConnector.getStore()?.getState().iab;
5292
+ if (!iab) return;
5293
+ iab.save().then(()=>{
5294
+ stateManager.addEvent({
5295
+ type: 'iab',
5296
+ message: 'IAB preferences saved'
5297
+ });
5298
+ }).catch((error)=>{
5299
+ stateManager.addEvent({
5300
+ type: 'error',
5301
+ message: `Failed to save IAB preferences: ${String(error)}`
5302
+ });
5303
+ });
5304
+ },
4543
5305
  onReset: async ()=>{
4544
5306
  const store = storeConnector.getStore();
4545
5307
  if (store) await resetAllConsents(store, stateManager);
@@ -4627,6 +5389,7 @@ function createDevTools(options = {}) {
4627
5389
  });
4628
5390
  break;
4629
5391
  }
5392
+ if (panel) panelHeightAnimator.animate(panel, previousPanelHeight);
4630
5393
  }
4631
5394
  storeConnector.subscribe(()=>{
4632
5395
  panelInstance.update();
@@ -4644,6 +5407,21 @@ function createDevTools(options = {}) {
4644
5407
  };
4645
5408
  },
4646
5409
  destroy: ()=>{
5410
+ const store = storeConnector.getStore();
5411
+ if (store) {
5412
+ store.getState().setCallback('onBannerFetched', originalCallbacks.onBannerFetched);
5413
+ store.getState().setCallback('onConsentSet', originalCallbacks.onConsentSet);
5414
+ store.getState().setCallback('onError', originalCallbacks.onError);
5415
+ store.getState().setCallback('onBeforeConsentRevocationReload', originalCallbacks.onBeforeConsentRevocationReload);
5416
+ if (hasWrappedNetworkBlockerCallback) {
5417
+ const currentNetworkBlocker = store.getState().networkBlocker;
5418
+ if (currentNetworkBlocker) store.getState().setNetworkBlocker({
5419
+ ...currentNetworkBlocker,
5420
+ onRequestBlocked: originalNetworkBlockedCallback
5421
+ });
5422
+ }
5423
+ }
5424
+ panelHeightAnimator.destroy();
4647
5425
  tabsInstance?.destroy();
4648
5426
  panelInstance.destroy();
4649
5427
  storeConnector.destroy();
@@ -4656,12 +5434,42 @@ function createDevTools(options = {}) {
4656
5434
  }
4657
5435
  function createDevToolsPanel(options) {
4658
5436
  const { namespace = 'c15tStore' } = options;
5437
+ let originalEmbeddedNetworkBlockedCallback;
5438
+ let hasWrappedEmbeddedNetworkBlocker = false;
4659
5439
  const stateManager = createStateManager({
4660
5440
  isOpen: true
4661
5441
  });
4662
5442
  const storeConnector = createStoreConnector({
4663
5443
  namespace,
4664
- onConnect: ()=>stateManager.setConnected(true),
5444
+ onConnect: (state, store)=>{
5445
+ stateManager.setConnected(true);
5446
+ const currentNetworkBlocker = state.networkBlocker;
5447
+ if (currentNetworkBlocker && !hasWrappedEmbeddedNetworkBlocker) {
5448
+ originalEmbeddedNetworkBlockedCallback = currentNetworkBlocker.onRequestBlocked;
5449
+ hasWrappedEmbeddedNetworkBlocker = true;
5450
+ store.getState().setNetworkBlocker({
5451
+ ...currentNetworkBlocker,
5452
+ onRequestBlocked: (payload)=>{
5453
+ stateManager.addEvent({
5454
+ type: 'network',
5455
+ message: getBlockedRequestMessage(payload),
5456
+ data: payload
5457
+ });
5458
+ if ('function' == typeof originalEmbeddedNetworkBlockedCallback) originalEmbeddedNetworkBlockedCallback(payload);
5459
+ }
5460
+ });
5461
+ }
5462
+ const persistedOverrides = loadPersistedOverrides();
5463
+ if (persistedOverrides) {
5464
+ const currentOverrides = normalizeOverridesForPersistence(state.overrides);
5465
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
5466
+ country: persistedOverrides.country,
5467
+ region: persistedOverrides.region,
5468
+ language: persistedOverrides.language,
5469
+ gpc: persistedOverrides.gpc
5470
+ });
5471
+ }
5472
+ },
4665
5473
  onDisconnect: ()=>stateManager.setConnected(false)
4666
5474
  });
4667
5475
  const container = renderer_div({
@@ -4710,29 +5518,61 @@ function createDevToolsPanel(options) {
4710
5518
  case 'location':
4711
5519
  renderLocationPanel(contentArea, {
4712
5520
  getState: getStoreState,
4713
- onSetOverrides: async (overrides)=>{
5521
+ onApplyOverrides: async (overrides)=>{
4714
5522
  const store = storeConnector.getStore();
4715
5523
  if (store) {
4716
- const current = store.getState().overrides || {};
4717
5524
  await store.getState().setOverrides({
4718
- ...current,
4719
- ...overrides
5525
+ country: overrides.country,
5526
+ region: overrides.region,
5527
+ language: overrides.language,
5528
+ gpc: overrides.gpc
5529
+ });
5530
+ persistOverrides({
5531
+ country: overrides.country,
5532
+ region: overrides.region,
5533
+ language: overrides.language,
5534
+ gpc: overrides.gpc
4720
5535
  });
4721
5536
  }
4722
5537
  },
4723
5538
  onClearOverrides: async ()=>{
4724
- await storeConnector.getStore()?.getState().setOverrides(void 0);
5539
+ await storeConnector.getStore()?.getState().setOverrides({
5540
+ country: void 0,
5541
+ region: void 0,
5542
+ language: void 0,
5543
+ gpc: void 0
5544
+ });
5545
+ clearPersistedOverrides();
4725
5546
  }
4726
5547
  });
4727
5548
  break;
4728
5549
  case "scripts":
4729
5550
  renderScriptsPanel(contentArea, {
4730
- getState: getStoreState
5551
+ getState: getStoreState,
5552
+ getEvents: ()=>stateManager.getState().eventLog
4731
5553
  });
4732
5554
  break;
4733
5555
  case 'iab':
4734
5556
  renderIabPanel(contentArea, {
4735
5557
  getState: getStoreState,
5558
+ onSetPurposeConsent: (purposeId, value)=>{
5559
+ storeConnector.getStore()?.getState().iab?.setPurposeConsent(purposeId, value);
5560
+ },
5561
+ onSetVendorConsent: (vendorId, value)=>{
5562
+ storeConnector.getStore()?.getState().iab?.setVendorConsent(vendorId, value);
5563
+ },
5564
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
5565
+ storeConnector.getStore()?.getState().iab?.setSpecialFeatureOptIn(featureId, value);
5566
+ },
5567
+ onAcceptAll: ()=>{
5568
+ storeConnector.getStore()?.getState().iab?.acceptAll();
5569
+ },
5570
+ onRejectAll: ()=>{
5571
+ storeConnector.getStore()?.getState().iab?.rejectAll();
5572
+ },
5573
+ onSave: ()=>{
5574
+ storeConnector.getStore()?.getState().iab?.save();
5575
+ },
4736
5576
  onReset: async ()=>{
4737
5577
  const store = storeConnector.getStore();
4738
5578
  if (store) await resetAllConsents(store);
@@ -4793,6 +5633,14 @@ function createDevToolsPanel(options) {
4793
5633
  return {
4794
5634
  element: container,
4795
5635
  destroy: ()=>{
5636
+ const store = storeConnector.getStore();
5637
+ if (store && hasWrappedEmbeddedNetworkBlocker) {
5638
+ const currentNetworkBlocker = store.getState().networkBlocker;
5639
+ if (currentNetworkBlocker) store.getState().setNetworkBlocker({
5640
+ ...currentNetworkBlocker,
5641
+ onRequestBlocked: originalEmbeddedNetworkBlockedCallback
5642
+ });
5643
+ }
4796
5644
  unsubscribe();
4797
5645
  tabsInstance.destroy();
4798
5646
  storeConnector.destroy();