@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/react.js CHANGED
@@ -346,22 +346,26 @@ var __webpack_modules__ = {
346
346
  justify-content: center;
347
347
  align-items: center;
348
348
  gap: var(--c15t-space-xs, .25rem);
349
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
350
349
  border: 1px solid var(--c15t-border, #e3e3e3);
351
350
  border-radius: var(--c15t-radius-md, .5rem);
352
351
  background-color: var(--c15t-surface, #fff);
352
+ min-height: 30px;
353
353
  color: var(--c15t-text, #171717);
354
354
  font-family: inherit;
355
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
355
+ font-size: 12px;
356
356
  font-weight: var(--c15t-font-weight-medium, 500);
357
357
  cursor: pointer;
358
- transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1));
358
+ transition: background-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), border-color var(--c15t-duration-fast, .1s) var(--c15t-easing, cubic-bezier(.4, 0, .2, 1)), 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));
359
+ padding: 5px 10px;
360
+ line-height: 1;
359
361
  display: inline-flex;
362
+ box-shadow: 0 1px 1px #0000000a;
360
363
  }
361
364
 
362
365
  .btn-evRVlh:hover {
363
366
  background-color: var(--c15t-surface-hover, #f7f7f7);
364
367
  border-color: var(--c15t-border-hover, #c9c9c9);
368
+ box-shadow: 0 2px 6px #00000014;
365
369
  }
366
370
 
367
371
  .btn-evRVlh:focus-visible {
@@ -369,9 +373,14 @@ var __webpack_modules__ = {
369
373
  outline-offset: 1px;
370
374
  }
371
375
 
376
+ .btn-evRVlh:active {
377
+ box-shadow: 0 1px 2px #00000014;
378
+ }
379
+
372
380
  .btn-evRVlh:disabled {
373
381
  opacity: .5;
374
382
  cursor: not-allowed;
383
+ box-shadow: none;
375
384
  }
376
385
 
377
386
  .btnPrimary-dA6nqY {
@@ -397,8 +406,10 @@ var __webpack_modules__ = {
397
406
  }
398
407
 
399
408
  .btnSmall-TjXoqZ {
400
- padding: 2px var(--c15t-space-xs, .25rem);
401
- font-size: 10px;
409
+ border-radius: var(--c15t-radius-sm, .375rem);
410
+ min-height: 26px;
411
+ padding: 3px 8px;
412
+ font-size: 11px;
402
413
  }
403
414
 
404
415
  .btnIcon-fiYQAh {
@@ -550,6 +561,50 @@ var __webpack_modules__ = {
550
561
  letter-spacing: .5px;
551
562
  }
552
563
 
564
+ .overrideField-keNdpJ {
565
+ flex-direction: column;
566
+ gap: 3px;
567
+ margin-bottom: 0;
568
+ display: flex;
569
+ }
570
+
571
+ .overrideLabel-ApMoTw {
572
+ color: var(--c15t-text-muted, #737373);
573
+ font-size: 11px;
574
+ font-weight: 600;
575
+ }
576
+
577
+ .overrideHint-yCfwGt {
578
+ color: var(--c15t-devtools-text-muted, #737373);
579
+ margin-top: 6px;
580
+ font-size: 11px;
581
+ }
582
+
583
+ .overrideActions-imdcn7 {
584
+ border-top: 1px dashed var(--c15t-border, #e3e3e3);
585
+ justify-content: space-between;
586
+ align-items: center;
587
+ gap: 8px;
588
+ margin-top: 8px;
589
+ padding-top: 8px;
590
+ display: flex;
591
+ }
592
+
593
+ .overrideActionButtons-gYOx1e {
594
+ flex-wrap: wrap;
595
+ gap: 6px;
596
+ display: flex;
597
+ }
598
+
599
+ .overrideStatus-sty_qS {
600
+ color: var(--c15t-text-muted, #737373);
601
+ font-size: 11px;
602
+ }
603
+
604
+ .overrideStatusDirty-OUdDMw {
605
+ color: var(--c15t-devtools-badge-warning, #f59f0a);
606
+ }
607
+
553
608
  .infoRow-RlB_0h {
554
609
  padding: var(--c15t-space-xs, .25rem) 0;
555
610
  justify-content: space-between;
@@ -642,6 +697,13 @@ var __webpack_modules__ = {
642
697
  section: "section-a197cB",
643
698
  sectionHeader: "sectionHeader-Xcljcw",
644
699
  sectionTitle: "sectionTitle-RUiFld",
700
+ overrideField: "overrideField-keNdpJ",
701
+ overrideLabel: "overrideLabel-ApMoTw",
702
+ overrideHint: "overrideHint-yCfwGt",
703
+ overrideActions: "overrideActions-imdcn7",
704
+ overrideActionButtons: "overrideActionButtons-gYOx1e",
705
+ overrideStatus: "overrideStatus-sty_qS",
706
+ overrideStatusDirty: "overrideStatusDirty-OUdDMw",
645
707
  infoRow: "infoRow-RlB_0h",
646
708
  infoLabel: "infoLabel-_pbK33",
647
709
  infoValue: "infoValue-flMl_e",
@@ -1124,16 +1186,23 @@ var __webpack_modules__ = {
1124
1186
  ___CSS_LOADER_EXPORT___.push([
1125
1187
  module.id,
1126
1188
  `.tabList-IyuiBE {
1189
+ align-items: center;
1127
1190
  gap: var(--c15t-space-xs, .25rem);
1128
- padding: var(--c15t-space-sm, .5rem) var(--c15t-space-md, 1rem);
1191
+ padding: var(--c15t-space-sm, .5rem) var(--c15t-space-sm, .5rem);
1129
1192
  border-bottom: 1px solid var(--c15t-border, #e3e3e3);
1130
1193
  background-color: var(--c15t-surface, #fff);
1131
1194
  scrollbar-width: none;
1132
1195
  -ms-overflow-style: none;
1196
+ scroll-padding-inline-end: var(--c15t-space-sm, .5rem);
1133
1197
  display: flex;
1134
1198
  overflow-x: auto;
1135
1199
  }
1136
1200
 
1201
+ .tabList-IyuiBE:after {
1202
+ content: "";
1203
+ flex: 0 0 var(--c15t-space-sm, .5rem);
1204
+ }
1205
+
1137
1206
  .tabList-IyuiBE::-webkit-scrollbar {
1138
1207
  display: none;
1139
1208
  }
@@ -1141,17 +1210,17 @@ var __webpack_modules__ = {
1141
1210
  .tab-yfDEqg {
1142
1211
  align-items: center;
1143
1212
  gap: var(--c15t-space-xs, .25rem);
1144
- padding: var(--c15t-space-xs, .25rem) var(--c15t-space-sm, .5rem);
1145
1213
  border-radius: var(--c15t-radius-md, .5rem);
1146
1214
  color: var(--c15t-text-muted, #737373);
1147
1215
  font-family: inherit;
1148
- font-size: var(--c15t-devtools-font-size-xs, .75rem);
1216
+ font-size: 11px;
1149
1217
  font-weight: var(--c15t-font-weight-medium, 500);
1150
1218
  cursor: pointer;
1151
1219
  white-space: nowrap;
1152
1220
  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));
1153
1221
  background-color: #0000;
1154
1222
  border: none;
1223
+ padding: 3px 7px;
1155
1224
  display: flex;
1156
1225
  }
1157
1226
 
@@ -1866,9 +1935,9 @@ function setPreferenceTriggerVisibility(visible) {
1866
1935
  const elements = getPreferenceTriggerElements();
1867
1936
  for (const el of elements)el.style.display = visible ? '' : 'none';
1868
1937
  }
1869
- function getPreferenceCenterOpener() {
1938
+ function getPreferenceCenterOpener(namespace = 'c15tStore') {
1870
1939
  const win = window;
1871
- const store = win.c15tStore;
1940
+ const store = win[namespace];
1872
1941
  if (store && 'function' == typeof store.getState) {
1873
1942
  const state = store.getState();
1874
1943
  if ('function' == typeof state.setActiveUI) return ()=>{
@@ -1877,6 +1946,7 @@ function getPreferenceCenterOpener() {
1877
1946
  }
1878
1947
  return null;
1879
1948
  }
1949
+ const version = '2.0.0-rc.3';
1880
1950
  const DEVTOOLS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 445" aria-label="c15t">
1881
1951
  <path fill="currentColor" d="M223.178.313c39.064 0 70.732 31.668 70.732 70.732-.001 39.064-31.668 70.731-70.732 70.731-12.181 0-23.642-3.079-33.649-8.502l-55.689 55.689a70.267 70.267 0 0 1 5.574 13.441h167.531c8.695-29.217 35.762-50.523 67.804-50.523 39.064 0 70.731 31.668 70.731 70.732s-31.668 70.732-70.731 70.732c-32.042 0-59.108-21.306-67.803-50.523H139.413a70.417 70.417 0 0 1-7.888 17.396l54.046 54.046c10.893-6.851 23.786-10.815 37.605-10.815 39.064 0 70.732 31.669 70.732 70.733 0 39.064-31.668 70.731-70.732 70.731s-70.732-31.667-70.732-70.731c0-10.518 2.296-20.499 6.414-29.471l-57.78-57.78c-8.972 4.117-18.952 6.414-29.47 6.414-39.063 0-70.731-31.668-70.732-70.732 0-39.064 31.669-70.732 70.733-70.732 12.18 0 23.642 3.079 33.649 8.502l55.688-55.688c-5.423-10.007-8.502-21.469-8.502-33.65 0-39.064 31.668-70.733 70.732-70.733Zm0 343.555c-16.742 0-30.314 13.572-30.314 30.314 0 16.741 13.572 30.313 30.314 30.313s30.314-13.572 30.314-30.313c0-16.742-13.572-30.314-30.314-30.314ZM71.611 192.299c-16.742 0-30.315 13.572-30.315 30.314s13.573 30.314 30.315 30.314c16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314Zm303.138 0c-16.729 0-30.294 13.551-30.315 30.275l.001.039-.001.038c.021 16.725 13.586 30.276 30.315 30.276 16.741 0 30.313-13.572 30.313-30.314 0-16.741-13.572-30.314-30.313-30.314ZM223.178 40.73c-16.742 0-30.314 13.573-30.314 30.315s13.573 30.313 30.314 30.313c16.742 0 30.313-13.572 30.314-30.313 0-16.742-13.572-30.314-30.314-30.315Z"/>
1882
1952
  </svg>`;
@@ -2080,7 +2150,7 @@ function getPositionClass(position) {
2080
2150
  }
2081
2151
  }
2082
2152
  function createPanel(options) {
2083
- const { stateManager, storeConnector, onRenderContent, enableUnifiedMode = true } = options;
2153
+ const { stateManager, storeConnector, onRenderContent, namespace = 'c15tStore', enableUnifiedMode = true } = options;
2084
2154
  let removePortal = null;
2085
2155
  let isAnimatingOut = false;
2086
2156
  let draggable = null;
@@ -2109,7 +2179,7 @@ function createPanel(options) {
2109
2179
  return;
2110
2180
  }
2111
2181
  hasPreferenceTrigger = detectPreferenceTrigger();
2112
- const preferenceCenterOpener = getPreferenceCenterOpener();
2182
+ const preferenceCenterOpener = getPreferenceCenterOpener(namespace);
2113
2183
  useUnifiedMode = hasPreferenceTrigger && null !== preferenceCenterOpener;
2114
2184
  if (useUnifiedMode && !dropdownMenu) {
2115
2185
  dropdownMenu = createDropdownMenu({
@@ -2129,7 +2199,7 @@ function createPanel(options) {
2129
2199
  description: 'Open privacy settings',
2130
2200
  icon: PREFERENCES_ICON,
2131
2201
  onClick: ()=>{
2132
- const opener = getPreferenceCenterOpener();
2202
+ const opener = getPreferenceCenterOpener(namespace);
2133
2203
  if (opener) opener();
2134
2204
  }
2135
2205
  },
@@ -2185,6 +2255,7 @@ function createPanel(options) {
2185
2255
  let panelElement = null;
2186
2256
  let backdropElement = null;
2187
2257
  let contentContainer = null;
2258
+ let footerElement = null;
2188
2259
  function createPanelElement() {
2189
2260
  const corner = draggable?.getCorner() ?? stateManager.getState().position;
2190
2261
  const positionClass = getPositionClass(corner);
@@ -2236,33 +2307,53 @@ function createPanel(options) {
2236
2307
  contentContainer = renderer_div({
2237
2308
  className: styles_panel_module.content
2238
2309
  });
2239
- const isConnected = storeConnector.isConnected();
2240
- const footer = renderer_div({
2241
- className: styles_panel_module.footer,
2242
- children: [
2243
- renderer_div({
2244
- className: styles_panel_module.footerStatus,
2245
- children: [
2246
- span({
2247
- className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2248
- }),
2249
- span({
2250
- text: isConnected ? 'Connected' : 'Disconnected'
2251
- })
2252
- ]
2253
- }),
2254
- span({
2255
- text: 'v1.8.3'
2256
- })
2257
- ]
2310
+ footerElement = renderer_div({
2311
+ className: styles_panel_module.footer
2258
2312
  });
2313
+ updateFooter();
2259
2314
  panel.appendChild(header);
2260
2315
  panel.appendChild(contentContainer);
2261
- panel.appendChild(footer);
2262
- if (isConnected) onRenderContent(contentContainer);
2316
+ panel.appendChild(footerElement);
2317
+ if (storeConnector.isConnected()) onRenderContent(contentContainer);
2263
2318
  else renderErrorState(contentContainer);
2264
2319
  return panel;
2265
2320
  }
2321
+ function updateFooter() {
2322
+ if (!footerElement) return;
2323
+ clearElement(footerElement);
2324
+ const isConnected = storeConnector.isConnected();
2325
+ const storeState = storeConnector.getState();
2326
+ const isLoading = storeState?.isLoadingConsentInfo ?? false;
2327
+ const statusChildren = [
2328
+ span({
2329
+ className: `${styles_panel_module.statusDot} ${isConnected ? styles_panel_module.statusConnected : styles_panel_module.statusDisconnected}`
2330
+ }),
2331
+ span({
2332
+ text: isConnected ? 'Connected' : 'Disconnected'
2333
+ })
2334
+ ];
2335
+ if (isLoading) statusChildren.push(span({
2336
+ style: {
2337
+ marginLeft: '4px',
2338
+ opacity: '0.7'
2339
+ },
2340
+ text: '\u00b7 Fetching /init\u2026'
2341
+ }));
2342
+ footerElement.appendChild(renderer_div({
2343
+ className: styles_panel_module.footerStatus,
2344
+ children: statusChildren
2345
+ }));
2346
+ if (!isConnected) footerElement.appendChild(renderer_button({
2347
+ className: styles_panel_module.closeButton,
2348
+ text: 'Retry',
2349
+ onClick: ()=>{
2350
+ storeConnector.retryConnection();
2351
+ }
2352
+ }));
2353
+ footerElement.appendChild(span({
2354
+ text: `v${version}`
2355
+ }));
2356
+ }
2266
2357
  function renderErrorState(container) {
2267
2358
  clearElement(container);
2268
2359
  const errorState = renderer_div({
@@ -2285,6 +2376,13 @@ function createPanel(options) {
2285
2376
  renderer_div({
2286
2377
  className: styles_panel_module.errorMessage,
2287
2378
  text: 'c15t consent manager is not initialized. Make sure you have set up the ConsentManagerProvider in your app.'
2379
+ }),
2380
+ renderer_button({
2381
+ className: styles_panel_module.closeButton,
2382
+ text: 'Retry Connection',
2383
+ onClick: ()=>{
2384
+ storeConnector.retryConnection();
2385
+ }
2288
2386
  })
2289
2387
  ]
2290
2388
  });
@@ -2320,6 +2418,7 @@ function createPanel(options) {
2320
2418
  panelElement = null;
2321
2419
  }
2322
2420
  contentContainer = null;
2421
+ footerElement = null;
2323
2422
  isAnimatingOut = false;
2324
2423
  floatingButton.style.display = '';
2325
2424
  stateManager.setOpen(false);
@@ -2338,6 +2437,7 @@ function createPanel(options) {
2338
2437
  update();
2339
2438
  });
2340
2439
  const unsubscribeStore = storeConnector.subscribe(()=>{
2440
+ updateFooter();
2341
2441
  if (contentContainer) if (storeConnector.isConnected()) onRenderContent(contentContainer);
2342
2442
  else renderErrorState(contentContainer);
2343
2443
  });
@@ -2993,13 +3093,13 @@ function consents_renderConsentsPanel(container, options) {
2993
3093
  },
2994
3094
  children: [
2995
3095
  createButton({
2996
- text: 'All',
3096
+ text: 'Accept',
2997
3097
  variant: 'primary',
2998
3098
  small: true,
2999
3099
  onClick: onAcceptAll
3000
3100
  }),
3001
3101
  createButton({
3002
- text: 'None',
3102
+ text: 'Reject',
3003
3103
  variant: 'default',
3004
3104
  small: true,
3005
3105
  onClick: onRejectAll
@@ -3047,16 +3147,22 @@ function consents_renderConsentsPanel(container, options) {
3047
3147
  function formatConsentName(name) {
3048
3148
  return name.replace(/_/g, ' ').replace(/\b\w/g, (l)=>l.toUpperCase());
3049
3149
  }
3150
+ let activeFilter = 'all';
3151
+ let selectedEventId = null;
3050
3152
  function events_renderEventsPanel(container, options) {
3051
3153
  const { getEvents, onClear } = options;
3052
3154
  clearElement(container);
3053
- const events = getEvents();
3155
+ const allEvents = getEvents();
3156
+ const events = allEvents.filter((event)=>matchesFilter(event, activeFilter));
3157
+ if (!events.some((event)=>event.id === selectedEventId)) selectedEventId = events[0]?.id ?? null;
3158
+ const selectedEvent = events.find((event)=>event.id === selectedEventId) ?? null;
3054
3159
  const header = renderer_div({
3055
3160
  style: {
3056
3161
  display: 'flex',
3057
3162
  alignItems: 'center',
3058
3163
  justifyContent: 'space-between',
3059
- padding: '12px 16px 8px'
3164
+ padding: '12px 16px 8px',
3165
+ gap: '8px'
3060
3166
  },
3061
3167
  children: [
3062
3168
  span({
@@ -3067,44 +3173,130 @@ function events_renderEventsPanel(container, options) {
3067
3173
  textTransform: 'uppercase',
3068
3174
  letterSpacing: '0.5px'
3069
3175
  },
3070
- text: `Events (${events.length})`
3176
+ text: `Events (${events.length}/${allEvents.length})`
3071
3177
  }),
3072
- createButton({
3073
- text: 'Clear',
3074
- small: true,
3075
- onClick: onClear
3178
+ renderer_div({
3179
+ style: {
3180
+ display: 'flex',
3181
+ gap: '6px'
3182
+ },
3183
+ children: [
3184
+ createButton({
3185
+ text: 'Export',
3186
+ small: true,
3187
+ onClick: ()=>exportEvents(allEvents)
3188
+ }),
3189
+ createButton({
3190
+ text: 'Clear',
3191
+ small: true,
3192
+ onClick: ()=>{
3193
+ onClear();
3194
+ selectedEventId = null;
3195
+ events_renderEventsPanel(container, options);
3196
+ }
3197
+ })
3198
+ ]
3076
3199
  })
3077
3200
  ]
3078
3201
  });
3079
3202
  container.appendChild(header);
3203
+ container.appendChild(renderer_div({
3204
+ style: {
3205
+ display: 'flex',
3206
+ flexWrap: 'wrap',
3207
+ gap: '6px',
3208
+ padding: '0 16px 8px'
3209
+ },
3210
+ children: EVENT_FILTERS.map((filter)=>createFilterButton(filter, filter === activeFilter, ()=>{
3211
+ activeFilter = filter;
3212
+ selectedEventId = null;
3213
+ events_renderEventsPanel(container, options);
3214
+ }))
3215
+ }));
3080
3216
  const eventList = renderer_div({
3081
3217
  style: {
3082
3218
  display: 'flex',
3083
3219
  flexDirection: 'column',
3084
3220
  gap: '4px',
3085
3221
  padding: '0 12px 12px',
3086
- maxHeight: '400px',
3222
+ maxHeight: '300px',
3087
3223
  overflowY: 'auto'
3088
3224
  }
3089
3225
  });
3090
- if (0 === events.length) {
3091
- const emptyState = renderer_div({
3092
- style: {
3093
- padding: '32px 16px',
3094
- textAlign: 'center',
3095
- color: 'var(--c15t-text-muted)',
3096
- fontSize: 'var(--c15t-devtools-font-size-sm)'
3097
- },
3098
- text: 'No events recorded yet'
3099
- });
3100
- eventList.appendChild(emptyState);
3101
- } else for (const event of events){
3102
- const eventItem = createEventItem(event);
3103
- eventList.appendChild(eventItem);
3104
- }
3226
+ if (0 === events.length) eventList.appendChild(renderer_div({
3227
+ style: {
3228
+ padding: '20px 16px',
3229
+ textAlign: 'center',
3230
+ color: 'var(--c15t-text-muted)',
3231
+ fontSize: 'var(--c15t-devtools-font-size-sm)'
3232
+ },
3233
+ text: 'No events match this filter'
3234
+ }));
3235
+ else for (const event of events)eventList.appendChild(createEventItem(event, event.id === selectedEventId, ()=>{
3236
+ selectedEventId = event.id;
3237
+ events_renderEventsPanel(container, options);
3238
+ }));
3105
3239
  container.appendChild(eventList);
3240
+ container.appendChild(createPayloadSection(selectedEvent));
3241
+ }
3242
+ const EVENT_FILTERS = [
3243
+ 'all',
3244
+ 'error',
3245
+ 'consent',
3246
+ 'network',
3247
+ 'iab'
3248
+ ];
3249
+ function createFilterButton(filter, active, onClick) {
3250
+ return createButton({
3251
+ text: filter.toUpperCase(),
3252
+ small: true,
3253
+ variant: active ? 'primary' : 'default',
3254
+ onClick
3255
+ });
3256
+ }
3257
+ function matchesFilter(event, filter) {
3258
+ if ('all' === filter) return true;
3259
+ if ('error' === filter) return 'error' === event.type;
3260
+ if ('consent' === filter) return 'consent_set' === event.type || 'consent_save' === event.type || 'consent_reset' === event.type;
3261
+ if ('network' === filter) return 'network' === event.type;
3262
+ return 'iab' === event.type;
3263
+ }
3264
+ function createPayloadSection(event) {
3265
+ const payload = event?.data ? JSON.stringify(event.data, null, 2) : null;
3266
+ return renderer_div({
3267
+ style: {
3268
+ padding: '0 12px 12px'
3269
+ },
3270
+ children: [
3271
+ renderer_div({
3272
+ style: {
3273
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3274
+ fontWeight: '600',
3275
+ color: 'var(--c15t-text-muted)',
3276
+ textTransform: 'uppercase',
3277
+ letterSpacing: '0.5px',
3278
+ marginBottom: '6px'
3279
+ },
3280
+ text: 'Payload'
3281
+ }),
3282
+ renderer_div({
3283
+ className: styles_components_module.gridCard ?? '',
3284
+ style: {
3285
+ padding: '8px',
3286
+ fontFamily: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace',
3287
+ fontSize: '11px',
3288
+ color: 'var(--c15t-text-muted)',
3289
+ maxHeight: '140px',
3290
+ overflowY: 'auto',
3291
+ whiteSpace: 'pre-wrap',
3292
+ wordBreak: 'break-word'
3293
+ },
3294
+ text: payload || 'Select an event with payload data'
3295
+ })
3296
+ ]
3297
+ });
3106
3298
  }
3107
- function createEventItem(event) {
3299
+ function createEventItem(event, selected, onSelect) {
3108
3300
  const time = formatTime(event.timestamp);
3109
3301
  const icon = getEventIcon(event.type);
3110
3302
  const color = getEventColor(event.type);
@@ -3115,8 +3307,11 @@ function createEventItem(event) {
3115
3307
  alignItems: 'center',
3116
3308
  gap: '8px',
3117
3309
  padding: '6px 10px',
3118
- fontSize: 'var(--c15t-devtools-font-size-xs)'
3310
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
3311
+ cursor: 'pointer',
3312
+ borderColor: selected ? 'var(--c15t-devtools-badge-info, #3b82f6)' : 'var(--c15t-border)'
3119
3313
  },
3314
+ onClick: onSelect,
3120
3315
  children: [
3121
3316
  span({
3122
3317
  style: {
@@ -3145,6 +3340,21 @@ function createEventItem(event) {
3145
3340
  ]
3146
3341
  });
3147
3342
  }
3343
+ function exportEvents(events) {
3344
+ const json = JSON.stringify(events, null, 2);
3345
+ const blob = new Blob([
3346
+ json
3347
+ ], {
3348
+ type: 'application/json'
3349
+ });
3350
+ const url = URL.createObjectURL(blob);
3351
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3352
+ const a = document.createElement('a');
3353
+ a.href = url;
3354
+ a.download = `c15t-events-${timestamp}.json`;
3355
+ a.click();
3356
+ URL.revokeObjectURL(url);
3357
+ }
3148
3358
  function formatTime(timestamp) {
3149
3359
  const date = new Date(timestamp);
3150
3360
  return date.toLocaleTimeString('en-US', {
@@ -3163,7 +3373,10 @@ function getEventIcon(type) {
3163
3373
  return '○';
3164
3374
  case 'error':
3165
3375
  return '✕';
3166
- case 'info':
3376
+ case 'network':
3377
+ return '◉';
3378
+ case 'iab':
3379
+ return '◆';
3167
3380
  default:
3168
3381
  return '○';
3169
3382
  }
@@ -3177,13 +3390,16 @@ function getEventColor(type) {
3177
3390
  return 'var(--c15t-devtools-badge-warning, #f59e0b)';
3178
3391
  case 'error':
3179
3392
  return 'var(--c15t-devtools-badge-error, #ef4444)';
3180
- case 'info':
3393
+ case 'network':
3394
+ return 'var(--c15t-devtools-badge-warning, #f59e0b)';
3395
+ case 'iab':
3396
+ return 'var(--c15t-devtools-badge-info, #3b82f6)';
3181
3397
  default:
3182
3398
  return 'var(--c15t-text-muted)';
3183
3399
  }
3184
3400
  }
3185
3401
  function iab_renderIabPanel(container, options) {
3186
- const { getState, onReset } = options;
3402
+ const { getState, onSetPurposeConsent, onSetVendorConsent, onSetSpecialFeatureOptIn, onAcceptAll, onRejectAll, onSave, onReset } = options;
3187
3403
  clearElement(container);
3188
3404
  const state = getState();
3189
3405
  if (!state) return void container.appendChild(renderer_div({
@@ -3261,7 +3477,9 @@ function iab_renderIabPanel(container, options) {
3261
3477
  for (const [purposeId, consent] of purposeEntries){
3262
3478
  const purposeInfo = purposes[purposeId];
3263
3479
  const purposeName = purposeInfo?.name || `Purpose ${purposeId}`;
3264
- purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent)));
3480
+ purposeList.appendChild(createPurposeRow(purposeId, purposeName, Boolean(consent), (value)=>{
3481
+ onSetPurposeConsent(Number(purposeId), value);
3482
+ }));
3265
3483
  }
3266
3484
  const purposesSection = createSection({
3267
3485
  title: `Purposes (${purposeEntries.length})`,
@@ -3287,7 +3505,9 @@ function iab_renderIabPanel(container, options) {
3287
3505
  for (const [featureId, optIn] of specialFeatureEntries){
3288
3506
  const featureInfo = specialFeatures[featureId];
3289
3507
  const featureName = featureInfo?.name || `Special Feature ${featureId}`;
3290
- specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn)));
3508
+ specialFeatureList.appendChild(createPurposeRow(featureId, featureName, Boolean(optIn), (value)=>{
3509
+ onSetSpecialFeatureOptIn(Number(featureId), value);
3510
+ }, 'feature'));
3291
3511
  }
3292
3512
  const specialFeaturesSection = createSection({
3293
3513
  title: `Special Features (${specialFeatureEntries.length})`,
@@ -3327,7 +3547,9 @@ function iab_renderIabPanel(container, options) {
3327
3547
  overflowY: 'auto'
3328
3548
  }
3329
3549
  });
3330
- for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab'));
3550
+ for (const [vendorId, consent, vendorName] of iabVendors)vendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'iab', (value)=>{
3551
+ onSetVendorConsent(Number(vendorId), value);
3552
+ }));
3331
3553
  const vendorsSection = createSection({
3332
3554
  title: `IAB Vendors (${iabVendors.length})`,
3333
3555
  children: [
@@ -3346,7 +3568,9 @@ function iab_renderIabPanel(container, options) {
3346
3568
  overflowY: 'auto'
3347
3569
  }
3348
3570
  });
3349
- for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom'));
3571
+ for (const [vendorId, consent, vendorName] of customVendors)customVendorList.appendChild(createVendorRow(vendorId, vendorName, consent, 'custom', (value)=>{
3572
+ onSetVendorConsent(vendorId, value);
3573
+ }));
3350
3574
  const customVendorsSection = createSection({
3351
3575
  title: `Custom Vendors (${customVendors.length})`,
3352
3576
  children: [
@@ -3368,15 +3592,40 @@ function iab_renderIabPanel(container, options) {
3368
3592
  style: {
3369
3593
  display: 'flex',
3370
3594
  alignItems: 'center',
3371
- justifyContent: 'flex-end',
3595
+ justifyContent: 'space-between',
3372
3596
  padding: '12px 16px',
3373
3597
  marginTop: 'auto',
3374
3598
  borderTop: '1px solid var(--c15t-border)',
3375
3599
  backgroundColor: 'var(--c15t-surface)'
3376
3600
  },
3377
3601
  children: [
3602
+ renderer_div({
3603
+ style: {
3604
+ display: 'flex',
3605
+ gap: '6px'
3606
+ },
3607
+ children: [
3608
+ createButton({
3609
+ text: 'Accept All',
3610
+ variant: 'primary',
3611
+ small: true,
3612
+ onClick: onAcceptAll
3613
+ }),
3614
+ createButton({
3615
+ text: 'Reject All',
3616
+ small: true,
3617
+ onClick: onRejectAll
3618
+ }),
3619
+ createButton({
3620
+ text: 'Save',
3621
+ variant: 'primary',
3622
+ small: true,
3623
+ onClick: onSave
3624
+ })
3625
+ ]
3626
+ }),
3378
3627
  createButton({
3379
- text: 'Reset All',
3628
+ text: 'Reset',
3380
3629
  variant: 'danger',
3381
3630
  small: true,
3382
3631
  onClick: onReset
@@ -3385,7 +3634,7 @@ function iab_renderIabPanel(container, options) {
3385
3634
  });
3386
3635
  container.appendChild(footer);
3387
3636
  }
3388
- function createPurposeRow(id, name, consent) {
3637
+ function createPurposeRow(id, name, consent, onChange, ariaKind = 'purpose') {
3389
3638
  return renderer_div({
3390
3639
  style: {
3391
3640
  display: 'flex',
@@ -3408,14 +3657,28 @@ function createPurposeRow(id, name, consent) {
3408
3657
  text: `${id}. ${name}`,
3409
3658
  title: name
3410
3659
  }),
3411
- createBadge({
3412
- text: consent ? '✓' : '✕',
3413
- variant: consent ? 'success' : 'error'
3660
+ renderer_div({
3661
+ style: {
3662
+ display: 'flex',
3663
+ alignItems: 'center',
3664
+ gap: '6px'
3665
+ },
3666
+ children: [
3667
+ createBadge({
3668
+ text: consent ? '✓' : '✕',
3669
+ variant: consent ? 'success' : 'error'
3670
+ }),
3671
+ createToggle({
3672
+ checked: consent,
3673
+ onChange,
3674
+ ariaLabel: `Toggle ${ariaKind} ${id}`
3675
+ })
3676
+ ]
3414
3677
  })
3415
3678
  ]
3416
3679
  });
3417
3680
  }
3418
- function createVendorRow(id, name, consent, type) {
3681
+ function createVendorRow(id, name, consent, type, onChange) {
3419
3682
  return renderer_div({
3420
3683
  style: {
3421
3684
  display: 'flex',
@@ -3462,16 +3725,21 @@ function createVendorRow(id, name, consent, type) {
3462
3725
  createBadge({
3463
3726
  text: consent ? '✓' : '✕',
3464
3727
  variant: consent ? 'success' : 'error'
3728
+ }),
3729
+ createToggle({
3730
+ checked: consent,
3731
+ onChange,
3732
+ ariaLabel: `Toggle vendor ${id}`
3465
3733
  })
3466
3734
  ]
3467
3735
  });
3468
3736
  }
3469
3737
  function truncateText(text, maxLength) {
3470
3738
  if (text.length <= maxLength) return text;
3471
- return text.slice(0, maxLength - 3) + '...';
3739
+ return `${text.slice(0, maxLength - 3)}...`;
3472
3740
  }
3473
3741
  function location_renderLocationPanel(container, options) {
3474
- const { getState, onSetOverrides, onClearOverrides } = options;
3742
+ const { getState, onApplyOverrides, onClearOverrides } = options;
3475
3743
  clearElement(container);
3476
3744
  const state = getState();
3477
3745
  if (!state) return void container.appendChild(renderer_div({
@@ -3492,145 +3760,230 @@ function location_renderLocationPanel(container, options) {
3492
3760
  createCompactInfoCard('Jurisdiction', locationInfo?.jurisdiction || '—'),
3493
3761
  createCompactInfoCard('Language', translationConfig?.defaultLanguage || '—')
3494
3762
  ];
3763
+ gridItems.push(createCompactInfoCard('GPC', getEffectiveGpcLabel(overrides?.gpc)));
3495
3764
  if (state.model) gridItems.push(createCompactInfoCard('Model', getModelLabel(state.model)));
3496
3765
  const locationGrid = createGrid({
3497
- columns: 2,
3766
+ columns: 3,
3498
3767
  children: gridItems
3499
3768
  });
3500
3769
  container.appendChild(locationGrid);
3770
+ const initialDraft = getDraftFromOverrides(overrides);
3771
+ let appliedOverrides = normalizeOverrideDraft(initialDraft);
3772
+ let isSubmitting = false;
3773
+ const countryField = createOverrideSelect({
3774
+ label: 'Country',
3775
+ selectOptions: COUNTRY_OPTIONS,
3776
+ value: initialDraft.country
3777
+ });
3778
+ const regionField = createOverrideInput({
3779
+ label: 'Region',
3780
+ placeholder: 'e.g., CA, NY, BE',
3781
+ value: initialDraft.region
3782
+ });
3783
+ const languageField = createOverrideInput({
3784
+ label: 'Language',
3785
+ placeholder: 'e.g., de, fr, en-US',
3786
+ value: initialDraft.language
3787
+ });
3788
+ const gpcField = createOverrideSelect({
3789
+ label: 'GPC',
3790
+ selectOptions: GPC_OPTIONS,
3791
+ value: initialDraft.gpc
3792
+ });
3793
+ const formStatus = span({
3794
+ className: styles_components_module.overrideStatus,
3795
+ text: 'In sync'
3796
+ });
3797
+ const applyButton = createButton({
3798
+ text: 'Apply',
3799
+ variant: 'primary',
3800
+ small: true,
3801
+ disabled: true,
3802
+ onClick: ()=>{
3803
+ applyDraft();
3804
+ }
3805
+ });
3806
+ const revertButton = createButton({
3807
+ text: 'Revert',
3808
+ small: true,
3809
+ disabled: true,
3810
+ onClick: ()=>{
3811
+ setDraftValues(getDraftFromOverrides(appliedOverrides));
3812
+ updateFormState();
3813
+ }
3814
+ });
3815
+ const clearButton = createButton({
3816
+ text: 'Clear',
3817
+ small: true,
3818
+ onClick: ()=>{
3819
+ clearDraftAndOverrides();
3820
+ }
3821
+ });
3822
+ const overrideFieldsGrid = renderer_div({
3823
+ style: {
3824
+ display: 'grid',
3825
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
3826
+ gap: '8px 10px'
3827
+ },
3828
+ children: [
3829
+ countryField.element,
3830
+ regionField.element,
3831
+ languageField.element,
3832
+ gpcField.element
3833
+ ]
3834
+ });
3501
3835
  const overrideSection = createSection({
3502
3836
  title: 'Override Settings',
3503
- actions: [
3504
- createButton({
3505
- text: 'Clear',
3506
- small: true,
3507
- onClick: onClearOverrides
3508
- })
3509
- ],
3510
3837
  children: [
3511
- createOverrideSelect({
3512
- label: 'Country',
3513
- selectOptions: COUNTRY_OPTIONS,
3514
- value: overrides?.country || '',
3515
- onChange: (value)=>onSetOverrides({
3516
- country: value || void 0
3517
- })
3518
- }),
3519
- createOverrideInput({
3520
- label: 'Region',
3521
- placeholder: 'e.g., CA, NY, BE',
3522
- value: overrides?.region || '',
3523
- onChange: (value)=>onSetOverrides({
3524
- region: value || void 0
3525
- })
3838
+ overrideFieldsGrid,
3839
+ span({
3840
+ className: styles_components_module.overrideHint,
3841
+ text: 'GPC override only affects opt-out or unregulated jurisdictions.'
3526
3842
  }),
3527
- createOverrideInput({
3528
- label: 'Language',
3529
- placeholder: 'e.g., de, fr, en',
3530
- value: overrides?.language || '',
3531
- onChange: (value)=>onSetOverrides({
3532
- language: value || void 0
3533
- })
3843
+ renderer_div({
3844
+ className: styles_components_module.overrideActions,
3845
+ children: [
3846
+ renderer_div({
3847
+ className: styles_components_module.overrideActionButtons,
3848
+ children: [
3849
+ revertButton,
3850
+ applyButton,
3851
+ clearButton
3852
+ ]
3853
+ }),
3854
+ formStatus
3855
+ ]
3534
3856
  })
3535
3857
  ]
3536
3858
  });
3537
3859
  container.appendChild(overrideSection);
3538
- const hasOverrides = overrides && (overrides.country || overrides.region || overrides.language);
3539
- if (hasOverrides) {
3540
- const overrideBanner = renderer_div({
3541
- style: {
3542
- padding: '8px 16px',
3543
- backgroundColor: 'var(--c15t-devtools-badge-info-bg)',
3544
- color: 'var(--c15t-devtools-badge-info)',
3545
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3546
- borderTop: '1px solid var(--c15t-devtools-border)'
3547
- },
3548
- text: 'Overrides are active. This may affect consent behavior.'
3860
+ countryField.control.addEventListener('change', updateFormState);
3861
+ regionField.control.addEventListener('input', updateFormState);
3862
+ languageField.control.addEventListener('input', updateFormState);
3863
+ gpcField.control.addEventListener('change', updateFormState);
3864
+ updateFormState();
3865
+ async function applyDraft() {
3866
+ if (isSubmitting) return;
3867
+ const draftOverrides = getDraftOverrides();
3868
+ if (overridesEqual(draftOverrides, appliedOverrides)) return;
3869
+ isSubmitting = true;
3870
+ updateFormState();
3871
+ try {
3872
+ await onApplyOverrides(draftOverrides);
3873
+ appliedOverrides = draftOverrides;
3874
+ } finally{
3875
+ isSubmitting = false;
3876
+ updateFormState();
3877
+ }
3878
+ }
3879
+ async function clearDraftAndOverrides() {
3880
+ if (isSubmitting) return;
3881
+ isSubmitting = true;
3882
+ updateFormState();
3883
+ try {
3884
+ await onClearOverrides();
3885
+ appliedOverrides = {};
3886
+ setDraftValues(getDraftFromOverrides(void 0));
3887
+ } finally{
3888
+ isSubmitting = false;
3889
+ updateFormState();
3890
+ }
3891
+ }
3892
+ function getDraftOverrides() {
3893
+ return normalizeOverrideDraft({
3894
+ country: countryField.control.value,
3895
+ region: regionField.control.value,
3896
+ language: languageField.control.value,
3897
+ gpc: gpcField.control.value
3549
3898
  });
3550
- container.appendChild(overrideBanner);
3899
+ }
3900
+ function setDraftValues(draft) {
3901
+ countryField.control.value = draft.country;
3902
+ regionField.control.value = draft.region;
3903
+ languageField.control.value = draft.language;
3904
+ gpcField.control.value = draft.gpc;
3905
+ }
3906
+ function updateFormState() {
3907
+ const draftOverrides = getDraftOverrides();
3908
+ const hasDraftChanges = !overridesEqual(draftOverrides, appliedOverrides);
3909
+ applyButton.disabled = !hasDraftChanges || isSubmitting;
3910
+ revertButton.disabled = !hasDraftChanges || isSubmitting;
3911
+ clearButton.disabled = isSubmitting;
3912
+ formStatus.textContent = isSubmitting ? 'Applying...' : hasDraftChanges ? 'Unsaved changes' : hasOverridesValue(appliedOverrides) ? 'Overrides active' : 'No overrides';
3913
+ if (styles_components_module.overrideStatusDirty) formStatus.classList.toggle(styles_components_module.overrideStatusDirty, !isSubmitting && hasDraftChanges);
3551
3914
  }
3552
3915
  }
3553
3916
  function createOverrideInput(options) {
3554
- const { label, placeholder, value, onChange } = options;
3555
- let debounceTimer = null;
3917
+ const { label, placeholder, value } = options;
3556
3918
  const inputField = input({
3557
3919
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3558
3920
  placeholder,
3559
- value,
3560
- onInput: (e)=>{
3561
- const target = e.target;
3562
- if (debounceTimer) clearTimeout(debounceTimer);
3563
- debounceTimer = setTimeout(()=>{
3564
- onChange(target.value);
3565
- }, 500);
3566
- }
3567
- });
3568
- return renderer_div({
3569
- style: {
3570
- display: 'flex',
3571
- alignItems: 'center',
3572
- justifyContent: 'space-between',
3573
- gap: '8px',
3574
- marginBottom: '8px'
3575
- },
3576
- children: [
3577
- renderer_div({
3578
- style: {
3579
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3580
- color: 'var(--c15t-devtools-text-muted)',
3581
- minWidth: '60px'
3582
- },
3583
- text: label
3584
- }),
3585
- renderer_div({
3586
- style: {
3587
- flex: '1'
3588
- },
3589
- children: [
3590
- inputField
3591
- ]
3592
- })
3593
- ]
3921
+ value
3594
3922
  });
3923
+ return {
3924
+ element: renderer_div({
3925
+ className: styles_components_module.overrideField,
3926
+ children: [
3927
+ span({
3928
+ className: styles_components_module.overrideLabel,
3929
+ text: label
3930
+ }),
3931
+ inputField
3932
+ ]
3933
+ }),
3934
+ control: inputField
3935
+ };
3595
3936
  }
3596
3937
  function createOverrideSelect(options) {
3597
- const { label, selectOptions, value, onChange } = options;
3938
+ const { label, selectOptions, value } = options;
3598
3939
  const selectField = renderer_select({
3599
3940
  className: `${styles_components_module.input ?? ''} ${styles_components_module.inputSmall ?? ''}`.trim(),
3600
3941
  options: selectOptions,
3601
- selectedValue: value,
3602
- onChange: (e)=>{
3603
- const target = e.target;
3604
- onChange(target.value);
3605
- }
3606
- });
3607
- return renderer_div({
3608
- style: {
3609
- display: 'flex',
3610
- alignItems: 'center',
3611
- justifyContent: 'space-between',
3612
- gap: '8px',
3613
- marginBottom: '8px'
3614
- },
3615
- children: [
3616
- renderer_div({
3617
- style: {
3618
- fontSize: 'var(--c15t-devtools-font-size-xs)',
3619
- color: 'var(--c15t-devtools-text-muted)',
3620
- minWidth: '60px'
3621
- },
3622
- text: label
3623
- }),
3624
- renderer_div({
3625
- style: {
3626
- flex: '1'
3627
- },
3628
- children: [
3629
- selectField
3630
- ]
3631
- })
3632
- ]
3942
+ selectedValue: value
3633
3943
  });
3944
+ return {
3945
+ element: renderer_div({
3946
+ className: styles_components_module.overrideField,
3947
+ children: [
3948
+ span({
3949
+ className: styles_components_module.overrideLabel,
3950
+ text: label
3951
+ }),
3952
+ selectField
3953
+ ]
3954
+ }),
3955
+ control: selectField
3956
+ };
3957
+ }
3958
+ function getDraftFromOverrides(overrides) {
3959
+ return {
3960
+ country: overrides?.country ?? '',
3961
+ region: overrides?.region ?? '',
3962
+ language: overrides?.language ?? '',
3963
+ gpc: overrides?.gpc === true ? 'true' : overrides?.gpc === false ? 'false' : ''
3964
+ };
3965
+ }
3966
+ function normalizeOverrideDraft(draft) {
3967
+ return {
3968
+ country: normalizeAlphaCode(draft.country),
3969
+ region: normalizeAlphaCode(draft.region),
3970
+ language: normalizeLanguageCode(draft.language),
3971
+ gpc: 'true' === draft.gpc ? true : 'false' === draft.gpc ? false : void 0
3972
+ };
3973
+ }
3974
+ function normalizeAlphaCode(value) {
3975
+ const normalized = value.trim().toUpperCase();
3976
+ return normalized || void 0;
3977
+ }
3978
+ function normalizeLanguageCode(value) {
3979
+ const normalized = value.trim();
3980
+ return normalized || void 0;
3981
+ }
3982
+ function overridesEqual(a, b) {
3983
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
3984
+ }
3985
+ function hasOverridesValue(overrides) {
3986
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
3634
3987
  }
3635
3988
  const COUNTRY_OPTIONS = [
3636
3989
  {
@@ -3754,6 +4107,32 @@ const COUNTRY_OPTIONS = [
3754
4107
  label: 'ZA - South Africa'
3755
4108
  }
3756
4109
  ];
4110
+ const GPC_OPTIONS = [
4111
+ {
4112
+ value: '',
4113
+ label: '-- Browser Default --'
4114
+ },
4115
+ {
4116
+ value: 'true',
4117
+ label: 'Force On (Simulated)'
4118
+ },
4119
+ {
4120
+ value: 'false',
4121
+ label: 'Force Off (Simulated)'
4122
+ }
4123
+ ];
4124
+ function getEffectiveGpcLabel(gpcOverride) {
4125
+ if (true === gpcOverride) return 'On (Override)';
4126
+ if (false === gpcOverride) return 'Off (Override)';
4127
+ if ('undefined' == typeof window || 'undefined' == typeof navigator) return 'Unknown';
4128
+ try {
4129
+ const nav = navigator;
4130
+ const value = nav.globalPrivacyControl;
4131
+ return true === value || '1' === value ? 'Active' : 'Inactive';
4132
+ } catch {
4133
+ return 'Unknown';
4134
+ }
4135
+ }
3757
4136
  function getModelLabel(model) {
3758
4137
  switch(model){
3759
4138
  case 'opt-in':
@@ -3770,9 +4149,11 @@ function createCompactInfoCard(label, value) {
3770
4149
  return renderer_div({
3771
4150
  className: styles_components_module.gridCard ?? '',
3772
4151
  style: {
4152
+ padding: '6px 8px',
4153
+ minHeight: 'auto',
3773
4154
  flexDirection: 'column',
3774
4155
  alignItems: 'flex-start',
3775
- gap: '2px'
4156
+ gap: '1px'
3776
4157
  },
3777
4158
  children: [
3778
4159
  span({
@@ -3797,34 +4178,38 @@ const dismissedResources = new Set();
3797
4178
  function scanDOM(state) {
3798
4179
  const results = [];
3799
4180
  const configuredScripts = state.scripts || [];
3800
- const managedDomains = new Map();
4181
+ const managedResources = [];
3801
4182
  for (const script of configuredScripts)if (script.src) try {
3802
4183
  const url = new URL(script.src, window.location.origin);
3803
- if (url.hostname !== window.location.hostname) managedDomains.set(url.hostname, script.id);
4184
+ if (url.hostname !== window.location.hostname) managedResources.push({
4185
+ scriptId: script.id,
4186
+ domain: url.hostname,
4187
+ pathPrefix: normalizePathname(url.pathname)
4188
+ });
3804
4189
  } catch {}
3805
4190
  const scriptElements = document.querySelectorAll("script[src]");
3806
4191
  for (const el of scriptElements){
3807
4192
  const src = el.getAttribute('src');
3808
4193
  if (!src) continue;
3809
- const resource = checkResource(src, "script", managedDomains);
4194
+ const resource = checkResource(src, "script", managedResources);
3810
4195
  if (resource) results.push(resource);
3811
4196
  }
3812
4197
  const iframeElements = document.querySelectorAll('iframe[src]');
3813
4198
  for (const el of iframeElements){
3814
4199
  const src = el.getAttribute('src');
3815
4200
  if (!src) continue;
3816
- const resource = checkResource(src, 'iframe', managedDomains);
4201
+ const resource = checkResource(src, 'iframe', managedResources);
3817
4202
  if (resource) results.push(resource);
3818
4203
  }
3819
4204
  return results;
3820
4205
  }
3821
- function checkResource(src, type, managedDomains) {
4206
+ function checkResource(src, type, managedResources) {
3822
4207
  try {
3823
4208
  const url = new URL(src, window.location.origin);
3824
4209
  const domain = url.hostname;
3825
4210
  if (domain === window.location.hostname) return null;
3826
4211
  if ('data:' === url.protocol || 'blob:' === url.protocol) return null;
3827
- const managedBy = managedDomains.get(domain);
4212
+ const managedBy = findManagedScriptId(url, managedResources);
3828
4213
  const isManaged = Boolean(managedBy);
3829
4214
  return {
3830
4215
  type,
@@ -3836,6 +4221,21 @@ function checkResource(src, type, managedDomains) {
3836
4221
  } catch {}
3837
4222
  return null;
3838
4223
  }
4224
+ function findManagedScriptId(url, managedResources) {
4225
+ const domain = url.hostname;
4226
+ const path = normalizePathname(url.pathname);
4227
+ let bestMatch = null;
4228
+ for (const matcher of managedResources)if (matcher.domain === domain) {
4229
+ if ('/' === matcher.pathPrefix || path.startsWith(matcher.pathPrefix)) {
4230
+ if (!bestMatch || matcher.pathPrefix.length > bestMatch.pathPrefix.length) bestMatch = matcher;
4231
+ }
4232
+ }
4233
+ return bestMatch?.scriptId;
4234
+ }
4235
+ function normalizePathname(pathname) {
4236
+ const trimmed = pathname.trim();
4237
+ return trimmed.length > 0 ? trimmed : '/';
4238
+ }
3839
4239
  function createDomScannerSection(state) {
3840
4240
  let resultsContainer = null;
3841
4241
  let lastScanResults = [];
@@ -4000,7 +4400,7 @@ const CODE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" f
4000
4400
  <polyline points="8 6 2 12 8 18"></polyline>
4001
4401
  </svg>`;
4002
4402
  function scripts_renderScriptsPanel(container, options) {
4003
- const { getState } = options;
4403
+ const { getState, getEvents } = options;
4004
4404
  clearElement(container);
4005
4405
  const state = getState();
4006
4406
  if (!state) return void container.appendChild(renderer_div({
@@ -4015,6 +4415,7 @@ function scripts_renderScriptsPanel(container, options) {
4015
4415
  const scripts = state.scripts || [];
4016
4416
  const loadedScripts = state.loadedScripts || {};
4017
4417
  const networkBlocker = state.networkBlocker;
4418
+ const events = getEvents?.() ?? [];
4018
4419
  if (0 === scripts.length) {
4019
4420
  const scriptsSection = createSection({
4020
4421
  title: 'Configured Scripts',
@@ -4097,6 +4498,20 @@ function scripts_renderScriptsPanel(container, options) {
4097
4498
  ]
4098
4499
  });
4099
4500
  container.appendChild(networkSection);
4501
+ const blockedRequestEvents = events.filter((event)=>'network' === event.type);
4502
+ const networkEventsSection = createSection({
4503
+ title: `Blocked Requests (${blockedRequestEvents.length})`,
4504
+ children: 0 === blockedRequestEvents.length ? [
4505
+ renderer_div({
4506
+ style: {
4507
+ fontSize: 'var(--c15t-devtools-font-size-xs)',
4508
+ color: 'var(--c15t-devtools-text-muted)'
4509
+ },
4510
+ text: 'No blocked network requests recorded in this session'
4511
+ })
4512
+ ] : createBlockedRequestContent(blockedRequestEvents)
4513
+ });
4514
+ container.appendChild(networkEventsSection);
4100
4515
  const loadedCount = Object.values(loadedScripts).filter(Boolean).length;
4101
4516
  const totalCount = scripts.length;
4102
4517
  const summarySection = createSection({
@@ -4122,12 +4537,127 @@ function scripts_renderScriptsPanel(container, options) {
4122
4537
  }
4123
4538
  function checkScriptConsent(state, category) {
4124
4539
  if (!category) return true;
4540
+ if ('function' == typeof state.has) try {
4541
+ return state.has(category);
4542
+ } catch {}
4125
4543
  if ('string' == typeof category) {
4126
4544
  const consents = state.consents || {};
4127
4545
  return true === consents[category];
4128
4546
  }
4129
4547
  return false;
4130
4548
  }
4549
+ function createBlockedRequestContent(events) {
4550
+ const stats = new Map();
4551
+ for (const event of events){
4552
+ const ruleId = getEventRuleId(event) ?? 'unknown';
4553
+ stats.set(ruleId, (stats.get(ruleId) ?? 0) + 1);
4554
+ }
4555
+ const statsList = renderer_div({
4556
+ style: {
4557
+ display: 'flex',
4558
+ flexDirection: 'column',
4559
+ gap: '4px',
4560
+ marginBottom: '8px'
4561
+ },
4562
+ children: [
4563
+ ...stats.entries()
4564
+ ].sort((a, b)=>b[1] - a[1]).map(([ruleId, count])=>createInfoRow({
4565
+ label: 'unknown' === ruleId ? 'Unknown Rule' : `Rule: ${ruleId}`,
4566
+ value: `${count}`
4567
+ }))
4568
+ });
4569
+ const latestEvents = events.slice(0, 5);
4570
+ const latestList = renderer_div({
4571
+ style: {
4572
+ display: 'flex',
4573
+ flexDirection: 'column',
4574
+ gap: '4px'
4575
+ },
4576
+ children: latestEvents.map((event)=>createInfoRow({
4577
+ label: `${formatEventTime(event.timestamp)} ${getEventMethod(event)}`,
4578
+ value: scripts_truncateText(getEventUrl(event), 38)
4579
+ }))
4580
+ });
4581
+ return [
4582
+ statsList,
4583
+ latestList
4584
+ ];
4585
+ }
4586
+ function getEventRuleId(event) {
4587
+ const data = event.data;
4588
+ const rule = data?.rule;
4589
+ const ruleId = rule?.id ?? data?.ruleId;
4590
+ return 'string' == typeof ruleId || 'number' == typeof ruleId ? String(ruleId) : void 0;
4591
+ }
4592
+ function getEventMethod(event) {
4593
+ const data = event.data;
4594
+ const method = data?.method;
4595
+ return 'string' == typeof method ? method.toUpperCase() : 'REQ';
4596
+ }
4597
+ function getEventUrl(event) {
4598
+ const data = event.data;
4599
+ const url = data?.url;
4600
+ return 'string' == typeof url ? url : event.message;
4601
+ }
4602
+ function formatEventTime(timestamp) {
4603
+ return new Date(timestamp).toLocaleTimeString('en-US', {
4604
+ hour12: false,
4605
+ hour: '2-digit',
4606
+ minute: '2-digit',
4607
+ second: '2-digit'
4608
+ });
4609
+ }
4610
+ function scripts_truncateText(text, maxLength) {
4611
+ if (text.length <= maxLength) return text;
4612
+ return `${text.slice(0, maxLength - 3)}...`;
4613
+ }
4614
+ const DEVTOOLS_OVERRIDES_STORAGE_KEY = 'c15t-devtools-overrides';
4615
+ function normalizeStringValue(value) {
4616
+ if ('string' != typeof value) return;
4617
+ const normalized = value.trim();
4618
+ return normalized.length > 0 ? normalized : void 0;
4619
+ }
4620
+ function normalizeBooleanValue(value) {
4621
+ return 'boolean' == typeof value ? value : void 0;
4622
+ }
4623
+ function normalizeOverrides(value) {
4624
+ if (!value || 'object' != typeof value) return null;
4625
+ const source = value;
4626
+ const overrides = {
4627
+ country: normalizeStringValue(source.country),
4628
+ region: normalizeStringValue(source.region),
4629
+ language: normalizeStringValue(source.language),
4630
+ gpc: normalizeBooleanValue(source.gpc)
4631
+ };
4632
+ return hasPersistedOverrides(overrides) ? overrides : null;
4633
+ }
4634
+ function hasPersistedOverrides(overrides) {
4635
+ return Boolean(overrides.country || overrides.region || overrides.language || void 0 !== overrides.gpc);
4636
+ }
4637
+ function override_storage_loadPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4638
+ if ('undefined' == typeof window) return null;
4639
+ try {
4640
+ const stored = localStorage.getItem(storageKey);
4641
+ if (!stored) return null;
4642
+ const parsed = JSON.parse(stored);
4643
+ return normalizeOverrides(parsed);
4644
+ } catch {
4645
+ return null;
4646
+ }
4647
+ }
4648
+ function override_storage_persistOverrides(overrides, storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4649
+ if ('undefined' == typeof window) return;
4650
+ try {
4651
+ if (!hasPersistedOverrides(overrides)) return void localStorage.removeItem(storageKey);
4652
+ localStorage.setItem(storageKey, JSON.stringify(overrides));
4653
+ } catch {}
4654
+ }
4655
+ function override_storage_clearPersistedOverrides(storageKey = DEVTOOLS_OVERRIDES_STORAGE_KEY) {
4656
+ if ('undefined' == typeof window) return;
4657
+ try {
4658
+ localStorage.removeItem(storageKey);
4659
+ } catch {}
4660
+ }
4131
4661
  const STORAGE_KEYS = {
4132
4662
  C15T: 'c15t',
4133
4663
  PENDING_SYNC: 'c15t:pending-consent-sync',
@@ -4265,12 +4795,37 @@ function store_connector_createStoreConnector(options = {}) {
4265
4795
  const { namespace = 'c15tStore', onConnect, onStateChange, onDisconnect } = options;
4266
4796
  let store = null;
4267
4797
  let unsubscribe = null;
4268
- let pollInterval = null;
4798
+ let reconnectTimeout = null;
4799
+ let reconnectAttempts = 0;
4800
+ let hasNotifiedDisconnect = false;
4269
4801
  const listeners = new Set();
4802
+ const INITIAL_RETRY_DELAY_MS = 100;
4803
+ const MAX_RETRY_DELAY_MS = 2000;
4804
+ const DISCONNECT_NOTIFY_ATTEMPTS = 5;
4805
+ function clearReconnectTimer() {
4806
+ if (reconnectTimeout) {
4807
+ clearTimeout(reconnectTimeout);
4808
+ reconnectTimeout = null;
4809
+ }
4810
+ }
4811
+ function resetReconnectState() {
4812
+ reconnectAttempts = 0;
4813
+ hasNotifiedDisconnect = false;
4814
+ }
4815
+ function notifyDisconnectedOnce() {
4816
+ if (hasNotifiedDisconnect) return;
4817
+ hasNotifiedDisconnect = true;
4818
+ onDisconnect?.();
4819
+ }
4270
4820
  function tryConnect() {
4271
4821
  if ('undefined' == typeof window) return false;
4272
4822
  const storeInstance = window[namespace];
4273
4823
  if (storeInstance && 'function' == typeof storeInstance.getState) {
4824
+ if (store === storeInstance && unsubscribe) return true;
4825
+ if (unsubscribe) {
4826
+ unsubscribe();
4827
+ unsubscribe = null;
4828
+ }
4274
4829
  store = storeInstance;
4275
4830
  unsubscribe = store.subscribe((state)=>{
4276
4831
  onStateChange?.(state);
@@ -4278,30 +4833,26 @@ function store_connector_createStoreConnector(options = {}) {
4278
4833
  });
4279
4834
  const currentState = store.getState();
4280
4835
  onConnect?.(currentState, store);
4281
- if (pollInterval) {
4282
- clearInterval(pollInterval);
4283
- pollInterval = null;
4284
- }
4836
+ clearReconnectTimer();
4837
+ resetReconnectState();
4285
4838
  return true;
4286
4839
  }
4287
4840
  return false;
4288
4841
  }
4842
+ function scheduleReconnect(immediate = false) {
4843
+ if (store || reconnectTimeout) return;
4844
+ const delay = immediate ? 0 : Math.min(INITIAL_RETRY_DELAY_MS * 2 ** Math.min(reconnectAttempts, 5), MAX_RETRY_DELAY_MS);
4845
+ reconnectTimeout = setTimeout(()=>{
4846
+ reconnectTimeout = null;
4847
+ reconnectAttempts++;
4848
+ if (tryConnect()) return;
4849
+ if (reconnectAttempts >= DISCONNECT_NOTIFY_ATTEMPTS) notifyDisconnectedOnce();
4850
+ scheduleReconnect();
4851
+ }, delay);
4852
+ }
4289
4853
  function startPolling() {
4290
- if (pollInterval) return;
4291
4854
  if (tryConnect()) return;
4292
- let attempts = 0;
4293
- const maxAttempts = 50;
4294
- pollInterval = setInterval(()=>{
4295
- attempts++;
4296
- if (tryConnect()) return;
4297
- if (attempts >= maxAttempts) {
4298
- if (pollInterval) {
4299
- clearInterval(pollInterval);
4300
- pollInterval = null;
4301
- }
4302
- onDisconnect?.();
4303
- }
4304
- }, 100);
4855
+ scheduleReconnect(true);
4305
4856
  }
4306
4857
  startPolling();
4307
4858
  return {
@@ -4315,11 +4866,13 @@ function store_connector_createStoreConnector(options = {}) {
4315
4866
  listeners.delete(listener);
4316
4867
  };
4317
4868
  },
4869
+ retryConnection: ()=>{
4870
+ if (store) return;
4871
+ resetReconnectState();
4872
+ scheduleReconnect(true);
4873
+ },
4318
4874
  destroy: ()=>{
4319
- if (pollInterval) {
4320
- clearInterval(pollInterval);
4321
- pollInterval = null;
4322
- }
4875
+ clearReconnectTimer();
4323
4876
  if (unsubscribe) {
4324
4877
  unsubscribe();
4325
4878
  unsubscribe = null;
@@ -4338,6 +4891,86 @@ tokens_options.domAPI = styleDomAPI_default();
4338
4891
  tokens_options.insertStyleElement = insertStyleElement_default();
4339
4892
  injectStylesIntoStyleTag_default()(tokens.A, tokens_options);
4340
4893
  tokens.A && tokens.A.locals && tokens.A.locals;
4894
+ const PANEL_HEIGHT_TRANSITION = 'height var(--c15t-duration-normal, 200ms) var(--c15t-easing, cubic-bezier(0.4, 0, 0.2, 1))';
4895
+ const PANEL_HEIGHT_TRANSITION_MS = 200;
4896
+ const PANEL_HEIGHT_TRANSITION_BUFFER_MS = 80;
4897
+ function normalizeOverridesForPersistence(overrides) {
4898
+ return {
4899
+ country: overrides?.country?.trim() || void 0,
4900
+ region: overrides?.region?.trim() || void 0,
4901
+ language: overrides?.language?.trim() || void 0,
4902
+ gpc: overrides?.gpc
4903
+ };
4904
+ }
4905
+ function persistedOverridesEqual(a, b) {
4906
+ return a.country === b.country && a.region === b.region && a.language === b.language && a.gpc === b.gpc;
4907
+ }
4908
+ function getBlockedRequestMessage(payload) {
4909
+ const data = payload;
4910
+ const method = 'string' == typeof data?.method ? data.method.toUpperCase() : 'REQUEST';
4911
+ const url = 'string' == typeof data?.url ? data.url : 'unknown-url';
4912
+ return `Network blocked: ${method} ${url}`;
4913
+ }
4914
+ function prefersReducedMotion() {
4915
+ return 'undefined' != typeof window && 'function' == typeof window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
4916
+ }
4917
+ function createPanelHeightAnimator() {
4918
+ let activePanel = null;
4919
+ let frameId = null;
4920
+ let timeoutId = null;
4921
+ let removeTransitionListener = null;
4922
+ function clearAnimationState() {
4923
+ if (null !== frameId) {
4924
+ window.cancelAnimationFrame(frameId);
4925
+ frameId = null;
4926
+ }
4927
+ if (null !== timeoutId) {
4928
+ clearTimeout(timeoutId);
4929
+ timeoutId = null;
4930
+ }
4931
+ if (removeTransitionListener) {
4932
+ removeTransitionListener();
4933
+ removeTransitionListener = null;
4934
+ }
4935
+ if (activePanel) {
4936
+ activePanel.style.height = '';
4937
+ activePanel.style.transition = '';
4938
+ activePanel.style.willChange = '';
4939
+ activePanel = null;
4940
+ }
4941
+ }
4942
+ function animate(panel, previousHeight) {
4943
+ if (!Number.isFinite(previousHeight) || prefersReducedMotion()) return;
4944
+ const nextHeight = panel.getBoundingClientRect().height;
4945
+ if (!Number.isFinite(nextHeight) || Math.abs(nextHeight - previousHeight) < 1) return;
4946
+ clearAnimationState();
4947
+ activePanel = panel;
4948
+ panel.style.height = `${previousHeight}px`;
4949
+ panel.style.willChange = 'height';
4950
+ panel.getBoundingClientRect();
4951
+ const handleTransitionEnd = (event)=>{
4952
+ const transitionEvent = event;
4953
+ if ('string' == typeof transitionEvent.propertyName && transitionEvent.propertyName && 'height' !== transitionEvent.propertyName) return;
4954
+ clearAnimationState();
4955
+ };
4956
+ panel.addEventListener('transitionend', handleTransitionEnd);
4957
+ removeTransitionListener = ()=>{
4958
+ panel.removeEventListener('transitionend', handleTransitionEnd);
4959
+ };
4960
+ frameId = window.requestAnimationFrame(()=>{
4961
+ frameId = null;
4962
+ panel.style.transition = PANEL_HEIGHT_TRANSITION;
4963
+ panel.style.height = `${nextHeight}px`;
4964
+ });
4965
+ timeoutId = setTimeout(()=>{
4966
+ clearAnimationState();
4967
+ }, PANEL_HEIGHT_TRANSITION_MS + PANEL_HEIGHT_TRANSITION_BUFFER_MS);
4968
+ }
4969
+ return {
4970
+ animate,
4971
+ destroy: clearAnimationState
4972
+ };
4973
+ }
4341
4974
  function createDevTools(options = {}) {
4342
4975
  const { namespace = 'c15tStore', position = 'bottom-right', defaultOpen = false } = options;
4343
4976
  const stateManager = state_manager_createStateManager({
@@ -4345,6 +4978,8 @@ function createDevTools(options = {}) {
4345
4978
  isOpen: defaultOpen
4346
4979
  });
4347
4980
  let originalCallbacks = {};
4981
+ let originalNetworkBlockedCallback;
4982
+ let hasWrappedNetworkBlockerCallback = false;
4348
4983
  const storeConnector = store_connector_createStoreConnector({
4349
4984
  namespace,
4350
4985
  onConnect: (state, store)=>{
@@ -4388,6 +5023,48 @@ function createDevTools(options = {}) {
4388
5023
  });
4389
5024
  if ('function' == typeof originalCallbacks.onBeforeConsentRevocationReload) originalCallbacks.onBeforeConsentRevocationReload(payload);
4390
5025
  });
5026
+ const currentNetworkBlocker = store.getState().networkBlocker;
5027
+ if (currentNetworkBlocker && !hasWrappedNetworkBlockerCallback) {
5028
+ originalNetworkBlockedCallback = currentNetworkBlocker.onRequestBlocked;
5029
+ hasWrappedNetworkBlockerCallback = true;
5030
+ store.getState().setNetworkBlocker({
5031
+ ...currentNetworkBlocker,
5032
+ onRequestBlocked: (payload)=>{
5033
+ stateManager.addEvent({
5034
+ type: 'network',
5035
+ message: getBlockedRequestMessage(payload),
5036
+ data: payload
5037
+ });
5038
+ if ('function' == typeof originalNetworkBlockedCallback) originalNetworkBlockedCallback(payload);
5039
+ }
5040
+ });
5041
+ }
5042
+ const persistedOverrides = override_storage_loadPersistedOverrides();
5043
+ if (persistedOverrides) {
5044
+ const currentOverrides = normalizeOverridesForPersistence(store.getState().overrides);
5045
+ if (!persistedOverridesEqual(persistedOverrides, currentOverrides)) store.getState().setOverrides({
5046
+ country: persistedOverrides.country,
5047
+ region: persistedOverrides.region,
5048
+ language: persistedOverrides.language,
5049
+ gpc: persistedOverrides.gpc
5050
+ }).then(()=>{
5051
+ stateManager.addEvent({
5052
+ type: 'info',
5053
+ message: 'Applied persisted devtools overrides',
5054
+ data: {
5055
+ country: persistedOverrides.country,
5056
+ region: persistedOverrides.region,
5057
+ language: persistedOverrides.language,
5058
+ gpc: persistedOverrides.gpc
5059
+ }
5060
+ });
5061
+ }).catch(()=>{
5062
+ stateManager.addEvent({
5063
+ type: 'error',
5064
+ message: 'Failed to apply persisted devtools overrides'
5065
+ });
5066
+ });
5067
+ }
4391
5068
  },
4392
5069
  onDisconnect: ()=>{
4393
5070
  stateManager.setConnected(false);
@@ -4399,14 +5076,18 @@ function createDevTools(options = {}) {
4399
5076
  onStateChange: ()=>{}
4400
5077
  });
4401
5078
  let tabsInstance = null;
5079
+ const panelHeightAnimator = createPanelHeightAnimator();
4402
5080
  const panelInstance = createPanel({
4403
5081
  stateManager,
4404
5082
  storeConnector,
5083
+ namespace,
4405
5084
  onRenderContent: (container)=>{
4406
5085
  renderContent(container, stateManager, storeConnector);
4407
5086
  }
4408
5087
  });
4409
5088
  function renderContent(container, stateManager, storeConnector) {
5089
+ const panel = container.parentElement;
5090
+ const previousPanelHeight = panel?.getBoundingClientRect().height ?? 0;
4410
5091
  clearElement(container);
4411
5092
  const storeState = storeConnector.getState();
4412
5093
  const disabledTabs = [];
@@ -4488,38 +5169,46 @@ function createDevTools(options = {}) {
4488
5169
  case 'location':
4489
5170
  location_renderLocationPanel(panelContent, {
4490
5171
  getState: getStoreState,
4491
- onSetOverrides: async (overrides)=>{
5172
+ onApplyOverrides: async (overrides)=>{
4492
5173
  const store = storeConnector.getStore();
4493
5174
  if (store) {
4494
- const currentOverrides = store.getState().overrides || {};
4495
5175
  await store.getState().setOverrides({
4496
- ...currentOverrides,
4497
- ...overrides
5176
+ country: overrides.country,
5177
+ region: overrides.region,
5178
+ language: overrides.language,
5179
+ gpc: overrides.gpc
4498
5180
  });
4499
- stateManager.addEvent({
4500
- type: 'info',
4501
- message: 'Overrides updated',
4502
- data: overrides
5181
+ override_storage_persistOverrides({
5182
+ country: overrides.country,
5183
+ region: overrides.region,
5184
+ language: overrides.language,
5185
+ gpc: overrides.gpc
4503
5186
  });
4504
- await store.getState().initConsentManager();
4505
5187
  stateManager.addEvent({
4506
5188
  type: 'info',
4507
- message: 'Consent manager re-initialized with new overrides'
5189
+ message: 'Overrides updated',
5190
+ data: {
5191
+ country: overrides.country,
5192
+ region: overrides.region,
5193
+ language: overrides.language,
5194
+ gpc: overrides.gpc
5195
+ }
4508
5196
  });
4509
5197
  }
4510
5198
  },
4511
5199
  onClearOverrides: async ()=>{
4512
5200
  const store = storeConnector.getStore();
4513
5201
  if (store) {
4514
- await store.getState().setOverrides(void 0);
4515
- stateManager.addEvent({
4516
- type: 'info',
4517
- message: 'Overrides cleared'
5202
+ await store.getState().setOverrides({
5203
+ country: void 0,
5204
+ region: void 0,
5205
+ language: void 0,
5206
+ gpc: void 0
4518
5207
  });
4519
- await store.getState().initConsentManager();
5208
+ override_storage_clearPersistedOverrides();
4520
5209
  stateManager.addEvent({
4521
5210
  type: 'info',
4522
- message: 'Consent manager re-initialized'
5211
+ message: 'Overrides cleared'
4523
5212
  });
4524
5213
  }
4525
5214
  }
@@ -4527,12 +5216,85 @@ function createDevTools(options = {}) {
4527
5216
  break;
4528
5217
  case "scripts":
4529
5218
  scripts_renderScriptsPanel(panelContent, {
4530
- getState: getStoreState
5219
+ getState: getStoreState,
5220
+ getEvents: ()=>stateManager.getState().eventLog
4531
5221
  });
4532
5222
  break;
4533
5223
  case 'iab':
4534
5224
  iab_renderIabPanel(panelContent, {
4535
5225
  getState: getStoreState,
5226
+ onSetPurposeConsent: (purposeId, value)=>{
5227
+ const iab = storeConnector.getStore()?.getState().iab;
5228
+ if (!iab) return;
5229
+ iab.setPurposeConsent(purposeId, value);
5230
+ stateManager.addEvent({
5231
+ type: 'iab',
5232
+ message: `IAB purpose ${purposeId} set to ${value}`,
5233
+ data: {
5234
+ purposeId,
5235
+ value
5236
+ }
5237
+ });
5238
+ },
5239
+ onSetVendorConsent: (vendorId, value)=>{
5240
+ const iab = storeConnector.getStore()?.getState().iab;
5241
+ if (!iab) return;
5242
+ iab.setVendorConsent(vendorId, value);
5243
+ stateManager.addEvent({
5244
+ type: 'iab',
5245
+ message: `IAB vendor ${vendorId} set to ${value}`,
5246
+ data: {
5247
+ vendorId,
5248
+ value
5249
+ }
5250
+ });
5251
+ },
5252
+ onSetSpecialFeatureOptIn: (featureId, value)=>{
5253
+ const iab = storeConnector.getStore()?.getState().iab;
5254
+ if (!iab) return;
5255
+ iab.setSpecialFeatureOptIn(featureId, value);
5256
+ stateManager.addEvent({
5257
+ type: 'iab',
5258
+ message: `IAB feature ${featureId} set to ${value}`,
5259
+ data: {
5260
+ featureId,
5261
+ value
5262
+ }
5263
+ });
5264
+ },
5265
+ onAcceptAll: ()=>{
5266
+ const iab = storeConnector.getStore()?.getState().iab;
5267
+ if (!iab) return;
5268
+ iab.acceptAll();
5269
+ stateManager.addEvent({
5270
+ type: 'iab',
5271
+ message: 'IAB accept all selected'
5272
+ });
5273
+ },
5274
+ onRejectAll: ()=>{
5275
+ const iab = storeConnector.getStore()?.getState().iab;
5276
+ if (!iab) return;
5277
+ iab.rejectAll();
5278
+ stateManager.addEvent({
5279
+ type: 'iab',
5280
+ message: 'IAB reject all selected'
5281
+ });
5282
+ },
5283
+ onSave: ()=>{
5284
+ const iab = storeConnector.getStore()?.getState().iab;
5285
+ if (!iab) return;
5286
+ iab.save().then(()=>{
5287
+ stateManager.addEvent({
5288
+ type: 'iab',
5289
+ message: 'IAB preferences saved'
5290
+ });
5291
+ }).catch((error)=>{
5292
+ stateManager.addEvent({
5293
+ type: 'error',
5294
+ message: `Failed to save IAB preferences: ${String(error)}`
5295
+ });
5296
+ });
5297
+ },
4536
5298
  onReset: async ()=>{
4537
5299
  const store = storeConnector.getStore();
4538
5300
  if (store) await reset_consents_resetAllConsents(store, stateManager);
@@ -4620,6 +5382,7 @@ function createDevTools(options = {}) {
4620
5382
  });
4621
5383
  break;
4622
5384
  }
5385
+ if (panel) panelHeightAnimator.animate(panel, previousPanelHeight);
4623
5386
  }
4624
5387
  storeConnector.subscribe(()=>{
4625
5388
  panelInstance.update();
@@ -4637,6 +5400,21 @@ function createDevTools(options = {}) {
4637
5400
  };
4638
5401
  },
4639
5402
  destroy: ()=>{
5403
+ const store = storeConnector.getStore();
5404
+ if (store) {
5405
+ store.getState().setCallback('onBannerFetched', originalCallbacks.onBannerFetched);
5406
+ store.getState().setCallback('onConsentSet', originalCallbacks.onConsentSet);
5407
+ store.getState().setCallback('onError', originalCallbacks.onError);
5408
+ store.getState().setCallback('onBeforeConsentRevocationReload', originalCallbacks.onBeforeConsentRevocationReload);
5409
+ if (hasWrappedNetworkBlockerCallback) {
5410
+ const currentNetworkBlocker = store.getState().networkBlocker;
5411
+ if (currentNetworkBlocker) store.getState().setNetworkBlocker({
5412
+ ...currentNetworkBlocker,
5413
+ onRequestBlocked: originalNetworkBlockedCallback
5414
+ });
5415
+ }
5416
+ }
5417
+ panelHeightAnimator.destroy();
4640
5418
  tabsInstance?.destroy();
4641
5419
  panelInstance.destroy();
4642
5420
  storeConnector.destroy();