@ecomconsult/consentkit 0.5.2 → 0.5.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.
@@ -28,7 +28,7 @@ export function undecidedState() {
28
28
  */
29
29
  export function createStub() {
30
30
  const stub = {
31
- version: '0.5.2',
31
+ version: '0.5.3',
32
32
  config: {},
33
33
  init: function () { return undecidedState(); },
34
34
  allowed: function (cat) { return cat === 'necessary'; },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecomconsult/consentkit",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "GDPR cookie consent core with blocking engine, Shadow DOM UI and Google Consent Mode v2. Zero dependencies, no build step.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/ck-core.js CHANGED
@@ -1461,7 +1461,7 @@
1461
1461
  // Public API
1462
1462
  // ---------------------------------------------------------------------------
1463
1463
  var ConsentKit = {
1464
- version: '0.5.2',
1464
+ version: '0.5.3',
1465
1465
  config: config,
1466
1466
 
1467
1467
  init: function (userConfig) {
package/src/ck-debug.js CHANGED
@@ -207,6 +207,7 @@
207
207
  themeFontInherit: 'наследуется',
208
208
  themeFontSystem: 'системный',
209
209
  themeFontPage: 'со страницы',
210
+ themeFontTry: 'попытка',
210
211
  themeRadius: 'скругления',
211
212
  themeCard: 'карточка',
212
213
  themeBtn: 'кнопки',
@@ -267,6 +268,7 @@
267
268
  themeFontInherit: 'inherited',
268
269
  themeFontSystem: 'system',
269
270
  themeFontPage: 'from the page',
271
+ themeFontTry: 'attempt',
270
272
  themeRadius: 'radii',
271
273
  themeCard: 'card',
272
274
  themeBtn: 'buttons',
@@ -607,12 +609,19 @@
607
609
  site whose body is unstyled is exactly the Times bug being diagnosed. */
608
610
  function fontRow(built, T) {
609
611
  if (built.font !== 'inherit') return built.font + ' (' + T.themeFontSystem + ')';
610
- var page = null;
612
+ var page = null, n = 0;
611
613
  try {
612
614
  if (CK && typeof CK._resolvePageFont === 'function') page = CK._resolvePageFont();
613
615
  } catch (e) { page = null; }
614
- return page ? page + ' (' + T.themeFontPage + ')'
615
- : 'inherit (' + T.themeFontInherit + ')';
616
+ // The attempt number is the whole point of the 0.5.3 re-probe: a page whose
617
+ // stylesheets land late resolves on look 2 or 3, and «попытка 1» vs
618
+ // «попытка 3» is what tells those two pages apart in the panel.
619
+ try {
620
+ if (CK && typeof CK._pageFontAttempt === 'function') n = CK._pageFontAttempt();
621
+ } catch (e) { n = 0; }
622
+ var tries = (typeof n === 'number' && n > 1) ? ', ' + T.themeFontTry + ' ' + n : '';
623
+ return page ? page + ' (' + T.themeFontPage + tries + ')'
624
+ : 'inherit (' + T.themeFontInherit + tries + ')';
616
625
  }
617
626
 
618
627
  // Resolved lazily, not at parse time: this file runs before ConsentKit.init()
package/src/ck-ui.js CHANGED
@@ -702,6 +702,40 @@
702
702
  return null;
703
703
  }
704
704
 
705
+ /* One probe at mount is not enough. On a page whose stylesheets are injected
706
+ by script (Tilda is the reported case) a CACHED reload mounts the banner
707
+ BEFORE those sheets apply, so every sample still computes to the UA default
708
+ and the probe returns null — the banner stays in Times and nothing looks
709
+ again. So the mount probe is only the first look: re-probes are scheduled
710
+ until the answer stops changing.
711
+
712
+ Both halves of that schedule are pure arithmetic, published on _contrast so
713
+ the behaviour is testable without timers or a DOM.
714
+
715
+ The timed ladder, in ms after mount. Deliberately front-loaded: a page that
716
+ styles itself quickly is corrected before a visitor can read the banner,
717
+ and the 4 s tail catches a slow webfont-driven sheet. `window.load` and
718
+ `document.fonts.ready` fire on their own and are NOT part of this ladder. */
719
+ var REPROBE_DELAYS = [500, 1500, 4000];
720
+
721
+ // Attempt is 1-based over the TIMED probes only; null means "no more timers".
722
+ function nextProbeDelay(attempt) {
723
+ if (typeof attempt !== 'number' || !isFinite(attempt) || attempt < 1) return null;
724
+ var i = Math.floor(attempt) - 1;
725
+ return i < REPROBE_DELAYS.length ? REPROBE_DELAYS[i] : null;
726
+ }
727
+
728
+ /* Should a freshly probed family replace what the banner is painting?
729
+ `current` is the applied value (null while the page still states nothing).
730
+ Only a non-null find is ever worth applying — a later null means the page
731
+ got LESS specific, which never happens for real and would otherwise throw
732
+ away a good answer. */
733
+ function shouldReprobe(current, found) {
734
+ if (typeof found !== 'string' || !found) return false;
735
+ if (typeof current !== 'string' || !current) return true; // null -> found
736
+ return normFamily(current) !== normFamily(found);
737
+ }
738
+
705
739
  // Ordered so the first hit is the most representative body text on the page.
706
740
  var FONT_PROBES = ['main p', 'article p', '[role=main] p', 'p', 'h1', 'h2', 'a', 'button', 'li'];
707
741
 
@@ -735,6 +769,12 @@
735
769
  // after a remount) quotes the value the banner actually painted.
736
770
  var pageFont = null;
737
771
 
772
+ /* How many times the SCHEDULER has looked, for the debug panel's «попытка N».
773
+ Counted here and not inside resolvePageFont() on purpose: _resolvePageFont()
774
+ re-runs the whole probe on every debug-panel render, and counting there would
775
+ report «попытка 14» after a few renders and exhaust the cap on nothing. */
776
+ var fontProbeCount = 1; // the mount probe is attempt 1
777
+
738
778
  function resolvePageFont() {
739
779
  if (typeof document === 'undefined') return pageFont;
740
780
  try {
@@ -766,6 +806,97 @@
766
806
  return pageFont;
767
807
  }
768
808
 
809
+ /* ------------------------------------------------ page-font re-probing */
810
+
811
+ /* Live timers, so remount() can cancel a schedule that belongs to the DOM it
812
+ is about to throw away. Everything here is guarded and only ever RUNS after
813
+ mount(), so the parse-time SSR context below never reaches it. */
814
+ var fontTimers = [];
815
+ var fontStable = false; // a non-null value survived one extra probe
816
+ var fontScheduled = false;
817
+
818
+ // Cap on SCHEDULED probes (the mount probe is not one of them): the three
819
+ // timed rungs plus window.load plus fonts.ready.
820
+ var MAX_REPROBES = 5;
821
+
822
+ function clearFontTimers() {
823
+ for (var i = 0; i < fontTimers.length; i++) {
824
+ try { clearTimeout(fontTimers[i]); } catch (e) { /* noop */ }
825
+ }
826
+ fontTimers = [];
827
+ }
828
+
829
+ /* One look. Re-applies the theme when the family changed, and reports whether
830
+ the answer has now held still for a whole extra probe — which is the only
831
+ thing that stops the ladder early. Applying and stopping are deliberately
832
+ separate: a first non-null find is painted IMMEDIATELY (the visitor must not
833
+ wait out the confirmation), the confirmation only decides about timers. */
834
+ function probeFontOnce() {
835
+ var before = pageFont;
836
+ var found = resolvePageFont(); // rewrites the pageFont cache
837
+ if (shouldReprobe(before, found)) {
838
+ // applyTheme() rewrites themeStyle.textContent wholesale, so the --ck-font
839
+ // rule is REPLACED rather than appended a second time.
840
+ try { applyTheme(safeConfig()); } catch (e) { /* noop */ }
841
+ fontStable = false;
842
+ return false;
843
+ }
844
+ // Unchanged. Stable only once we have an actual family in hand: a page that
845
+ // keeps answering null is exactly the case that has to keep looking.
846
+ if (found) { fontStable = true; return true; }
847
+ return false;
848
+ }
849
+
850
+ /* Run a scheduled probe. Every trigger — window.load, fonts.ready and each
851
+ rung of the timed ladder — funnels through here so the cap and the counter
852
+ are counted in one place. */
853
+ function runScheduledProbe() {
854
+ if (!mounted || fontStable) return;
855
+ if (fontProbeCount >= 1 + MAX_REPROBES) return;
856
+ fontProbeCount++;
857
+ if (probeFontOnce()) clearFontTimers(); // settled: drop the rest
858
+ }
859
+
860
+ /* Called once from the end of mount(). Not from applyTheme(): that runs again
861
+ on every palette-only ck:init, which would restart the whole ladder. */
862
+ function scheduleFontProbes(cfg) {
863
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
864
+ if (fontScheduled) return;
865
+ // theme.font:'system' is a fixed stack — there is nothing to sample.
866
+ try {
867
+ var t = (cfg && cfg.theme) || {};
868
+ if (resolveFont(t) !== 'inherit') return;
869
+ } catch (e) { return; }
870
+
871
+ fontScheduled = true;
872
+ try {
873
+ for (var a = 1; ; a++) {
874
+ var d = nextProbeDelay(a);
875
+ if (d === null) break;
876
+ fontTimers.push(setTimeout(runScheduledProbe, d));
877
+ }
878
+ } catch (e) { /* noop */ }
879
+
880
+ // A cached reload's mount can precede the page's own stylesheets; `load`
881
+ // is the first moment every <link> in the document has definitely applied.
882
+ try {
883
+ if (document.readyState === 'complete') {
884
+ // Already past it — the ladder covers this case on its own.
885
+ } else {
886
+ window.addEventListener('load', function () { runScheduledProbe(); }, { once: true });
887
+ }
888
+ } catch (e) { /* noop */ }
889
+
890
+ // A webfont swapping in can change the computed family outright.
891
+ try {
892
+ if (document.fonts && typeof document.fonts.ready === 'object' &&
893
+ document.fonts.ready && typeof document.fonts.ready.then === 'function') {
894
+ document.fonts.ready.then(function () { runScheduledProbe(); },
895
+ function () { /* noop */ });
896
+ }
897
+ } catch (e) { /* noop */ }
898
+ }
899
+
769
900
  /* --------------------------------------------------- button resolution */
770
901
 
771
902
  var BTN_ROLES = ['accept', 'reject', 'settings'];
@@ -1030,7 +1161,11 @@
1030
1161
  var fontCss = '';
1031
1162
  if (built.font === 'inherit') {
1032
1163
  var page = resolvePageFont();
1033
- if (page) fontCss = '\n:host{--ck-font:' + page + '}';
1164
+ // Owner rule (2026-09-06): the page's font when it can be read, the
1165
+ // system stack when it cannot — never a bare `inherit`, which on Tilda
1166
+ // and friends resolves to <body>'s browser default (Times). A later
1167
+ // re-probe that finds the page font swaps the system stack out again.
1168
+ fontCss = '\n:host{--ck-font:' + (page || SYSTEM_FONT) + '}';
1034
1169
  } else {
1035
1170
  pageFont = null; // theme.font:'system' opts out
1036
1171
  }
@@ -1407,6 +1542,21 @@
1407
1542
 
1408
1543
  function openPanel(invoker) {
1409
1544
  if (!mounted || !nodes.panel) return;
1545
+ /* A visitor who opens the settings long after load is the last chance to
1546
+ get the typeface right, and by then the page is certainly styled. Only
1547
+ when the banner is still on `inherit` — a resolved family is left alone
1548
+ so opening the panel can never restyle a correct banner. Hooked here
1549
+ rather than on the ck:ui:open-preferences listener because the banner's
1550
+ own «Настроить» button calls openPanel() directly.
1551
+
1552
+ Gated on fontScheduled, which is only ever set for a theme that actually
1553
+ samples the page: with theme.font:'system' applyTheme() nulls pageFont on
1554
+ every call, so without this the condition would be permanently true and
1555
+ every panel open would re-probe the DOM and rewrite the sheet to produce
1556
+ byte-identical CSS, forever. */
1557
+ if (fontScheduled && !pageFont && !fontStable) {
1558
+ try { probeFontOnce(); } catch (e) { /* noop */ }
1559
+ }
1410
1560
  lastFocus = invoker || root.activeElement || document.activeElement;
1411
1561
  syncSwitches(safeState());
1412
1562
  nodes.panelScrim.classList.remove('ck-hidden');
@@ -1482,6 +1632,12 @@
1482
1632
  mounted = false;
1483
1633
  panelOpen = false;
1484
1634
  lastFocus = null;
1635
+ // The pending schedule belongs to the shadow root about to be rebuilt; the
1636
+ // fresh mount() starts its own ladder from a clean count.
1637
+ clearFontTimers();
1638
+ fontScheduled = false;
1639
+ fontStable = false;
1640
+ fontProbeCount = 1;
1485
1641
  mount(cfg);
1486
1642
  }
1487
1643
 
@@ -1526,6 +1682,10 @@
1526
1682
  mounted = true;
1527
1683
  mountedSig = signature(cfg);
1528
1684
  syncFromState();
1685
+
1686
+ // Only now: scheduleFontProbes() re-applies the theme from a timer, and
1687
+ // applyTheme() is a no-op until there is a host and a themeStyle to write.
1688
+ scheduleFontProbes(cfg);
1529
1689
  }
1530
1690
 
1531
1691
  /* ---------------------------------------------------------------- events */
@@ -1553,6 +1713,8 @@
1553
1713
  resolveRadius: resolveRadius,
1554
1714
  resolveFont: resolveFont,
1555
1715
  pickPageFont: pickPageFont,
1716
+ nextProbeDelay: nextProbeDelay,
1717
+ shouldReprobe: shouldReprobe,
1556
1718
  resolveDetails: resolveDetails,
1557
1719
  buildThemeCss: buildThemeCss
1558
1720
  };
@@ -1563,6 +1725,10 @@
1563
1725
  ck._resolvePageFont = function () {
1564
1726
  return (typeof document === 'undefined' || !root) ? pageFont : resolvePageFont();
1565
1727
  };
1728
+ // How many times the SCHEDULER has looked (1 = the mount probe alone). The
1729
+ // debug panel prints it: on a Tilda-style page the interesting fact is that
1730
+ // the family arrived on the third look, not on the first.
1731
+ ck._pageFontAttempt = function () { return fontProbeCount; };
1566
1732
  })();
1567
1733
 
1568
1734
  // SSR-safe: with no DOM there is nothing to render or listen to, so importing