devtools-flag.ts 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. // =============================================================================
  2. // Devtools Active Flag
  3. // =============================================================================
  4. //
  5. // A tiny module-level counter that adapters increment while devtools is
  6. // mounted. Framework renderers read it to decide whether to add the
  7. // `data-jr-key` attribute that lets the picker map DOM nodes back to
  8. // spec element keys.
  9. //
  10. // Purely opt-in; when no devtools is mounted the counter stays at 0 and
  11. // renderers behave exactly as before.
  12. // =============================================================================
  13. let activeCount = 0;
  14. const listeners = new Set<() => void>();
  15. /**
  16. * Mark devtools as active. Returns a release function. Safe to call
  17. * multiple times (nested counter).
  18. *
  19. * @example
  20. * ```ts
  21. * // In a devtools adapter:
  22. * useEffect(() => markDevtoolsActive(), []);
  23. * ```
  24. */
  25. export function markDevtoolsActive(): () => void {
  26. activeCount += 1;
  27. notifyDevtoolsActiveChange();
  28. let released = false;
  29. return () => {
  30. if (released) return;
  31. released = true;
  32. activeCount = Math.max(0, activeCount - 1);
  33. notifyDevtoolsActiveChange();
  34. };
  35. }
  36. /**
  37. * True when at least one devtools adapter is mounted in the page.
  38. * Cheap to call; a plain integer comparison.
  39. */
  40. export function isDevtoolsActive(): boolean {
  41. return activeCount > 0;
  42. }
  43. /**
  44. * Subscribe to changes in the devtools-active flag. Framework renderers
  45. * that need to re-render on state change (e.g. React's `useSyncExternalStore`)
  46. * subscribe here; most can just read `isDevtoolsActive()` on render.
  47. */
  48. export function subscribeDevtoolsActive(listener: () => void): () => void {
  49. listeners.add(listener);
  50. return () => {
  51. listeners.delete(listener);
  52. };
  53. }
  54. function notifyDevtoolsActiveChange() {
  55. for (const l of listeners) {
  56. try {
  57. l();
  58. } catch (err) {
  59. if (process.env.NODE_ENV !== "production") {
  60. console.error("[json-render] devtools-active listener threw:", err);
  61. }
  62. }
  63. }
  64. }