@intlayer/design-system 9.3.1 → 9.3.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 (129) hide show
  1. package/dist/esm/api/hooks/auth.mjs +2 -0
  2. package/dist/esm/api/hooks/auth.mjs.map +1 -1
  3. package/dist/esm/api/index.mjs +2 -2
  4. package/dist/esm/components/Browser/Browser.mjs +1 -1
  5. package/dist/esm/components/Browser/Browser.mjs.map +1 -1
  6. package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
  7. package/dist/esm/components/CopyButton/index.mjs +2 -1
  8. package/dist/esm/components/CopyButton/index.mjs.map +1 -1
  9. package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
  10. package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
  11. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
  12. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
  13. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
  14. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs +1 -1
  15. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs.map +1 -1
  16. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
  17. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
  18. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs +1 -1
  19. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs.map +1 -1
  20. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
  21. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
  22. package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
  23. package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
  24. package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
  25. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
  26. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
  27. package/dist/esm/components/Form/FormBase.mjs.map +1 -1
  28. package/dist/esm/components/Form/elements/FormElementWrapper.mjs +1 -1
  29. package/dist/esm/components/Form/elements/FormElementWrapper.mjs.map +1 -1
  30. package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
  31. package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
  32. package/dist/esm/components/IDE/Code.mjs +3 -3
  33. package/dist/esm/components/IDE/Code.mjs.map +1 -1
  34. package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
  35. package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
  36. package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
  37. package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
  38. package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
  39. package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
  40. package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
  41. package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
  42. package/dist/esm/components/IDE/IDE.mjs +2 -5
  43. package/dist/esm/components/IDE/IDE.mjs.map +1 -1
  44. package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
  45. package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
  46. package/dist/esm/components/IDE/index.mjs +2 -2
  47. package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
  48. package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
  49. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
  50. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
  51. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
  52. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
  53. package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
  54. package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
  55. package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
  56. package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
  57. package/dist/esm/components/Pattern/GridDistortionPattern.mjs +100 -15
  58. package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
  59. package/dist/esm/components/Popover/dynamic.mjs +5 -4
  60. package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
  61. package/dist/esm/components/Popover/index.mjs +2 -2
  62. package/dist/esm/components/Popover/static.mjs +47 -18
  63. package/dist/esm/components/Popover/static.mjs.map +1 -1
  64. package/dist/esm/components/Tab/Tab.mjs +37 -16
  65. package/dist/esm/components/Tab/Tab.mjs.map +1 -1
  66. package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
  67. package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
  68. package/dist/esm/components/index.mjs +3 -3
  69. package/dist/esm/hooks/useDevice.mjs +16 -6
  70. package/dist/esm/hooks/useDevice.mjs.map +1 -1
  71. package/dist/esm/hooks/useItemSelector.mjs +107 -112
  72. package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
  73. package/dist/esm/libs/auth.mjs +16 -3
  74. package/dist/esm/libs/auth.mjs.map +1 -1
  75. package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
  76. package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
  77. package/dist/types/api/hooks/auth.d.ts.map +1 -1
  78. package/dist/types/components/Browser/Browser.d.ts +7 -1
  79. package/dist/types/components/Browser/Browser.d.ts.map +1 -1
  80. package/dist/types/components/CopyButton/index.d.ts +9 -3
  81. package/dist/types/components/CopyButton/index.d.ts.map +1 -1
  82. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
  83. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
  84. package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
  85. package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
  86. package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.d.ts +1 -1
  87. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
  88. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
  89. package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.d.ts +1 -1
  90. package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
  91. package/dist/types/components/Form/FormBase.d.ts +1 -1
  92. package/dist/types/components/Form/elements/FormElementWrapper.d.ts +1 -1
  93. package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
  94. package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
  95. package/dist/types/components/IDE/Code.d.ts +6 -1
  96. package/dist/types/components/IDE/Code.d.ts.map +1 -1
  97. package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
  98. package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
  99. package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
  100. package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
  101. package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
  102. package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
  103. package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
  104. package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
  105. package/dist/types/components/IDE/IDE.d.ts +0 -1
  106. package/dist/types/components/IDE/IDE.d.ts.map +1 -1
  107. package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
  108. package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
  109. package/dist/types/components/IDE/index.d.ts +2 -2
  110. package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
  111. package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
  112. package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
  113. package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
  114. package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
  115. package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
  116. package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
  117. package/dist/types/components/Popover/index.d.ts +2 -2
  118. package/dist/types/components/Popover/static.d.ts +6 -1
  119. package/dist/types/components/Popover/static.d.ts.map +1 -1
  120. package/dist/types/components/Tab/Tab.d.ts +2 -2
  121. package/dist/types/components/Tab/Tab.d.ts.map +1 -1
  122. package/dist/types/components/index.d.ts +3 -3
  123. package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
  124. package/dist/types/libs/auth.d.ts +3 -0
  125. package/dist/types/libs/auth.d.ts.map +1 -1
  126. package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
  127. package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
  128. package/package.json +43 -38
  129. package/tailwind.css +27 -1
@@ -105,29 +105,53 @@ const buildSegmentAttrs = (startX, startY, endX, endY, mouseX, mouseY, mouseActi
105
105
  strokeWidth
106
106
  };
107
107
  };
108
+ /**
109
+ * Advances every point one step toward its target and reports how far the
110
+ * furthest one travelled.
111
+ *
112
+ * The caller uses that distance to decide whether another frame is worth
113
+ * running: the relaxation is exponential, so once it drops below
114
+ * {@link SETTLE_THRESHOLD_PX} the grid is visually at rest and redrawing it
115
+ * only burns the main thread.
116
+ *
117
+ * @returns Largest per-axis distance any point moved this step, in pixels.
118
+ */
108
119
  const updateGridPoints = (grid, mouseX, mouseY, mouseActive, radius, strength) => {
109
120
  const rows = grid.length;
110
121
  const columns = grid[0].length;
122
+ let largestMovement = 0;
111
123
  for (let rowIndex = 0; rowIndex < rows; rowIndex++) for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
112
124
  const point = grid[rowIndex][columnIndex];
125
+ const previousX = point.x;
126
+ const previousY = point.y;
113
127
  if (!mouseActive) {
114
128
  point.x += (point.baseX - point.x) * .08;
115
129
  point.y += (point.baseY - point.y) * .08;
116
- continue;
117
- }
118
- const deltaX = mouseX - point.baseX;
119
- const deltaY = mouseY - point.baseY;
120
- const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
121
- if (distance < radius && distance > 0) {
122
- const displacement = (Math.cos(distance / radius * Math.PI) + 1) / 2 * strength * radius * .3;
123
- point.x += (point.baseX + deltaX / distance * displacement - point.x) * .12;
124
- point.y += (point.baseY + deltaY / distance * displacement - point.y) * .12;
125
130
  } else {
126
- point.x += (point.baseX - point.x) * .08;
127
- point.y += (point.baseY - point.y) * .08;
131
+ const deltaX = mouseX - point.baseX;
132
+ const deltaY = mouseY - point.baseY;
133
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
134
+ if (distance < radius && distance > 0) {
135
+ const displacement = (Math.cos(distance / radius * Math.PI) + 1) / 2 * strength * radius * .3;
136
+ point.x += (point.baseX + deltaX / distance * displacement - point.x) * .12;
137
+ point.y += (point.baseY + deltaY / distance * displacement - point.y) * .12;
138
+ } else {
139
+ point.x += (point.baseX - point.x) * .08;
140
+ point.y += (point.baseY - point.y) * .08;
141
+ }
128
142
  }
143
+ const movement = Math.max(Math.abs(point.x - previousX), Math.abs(point.y - previousY));
144
+ if (movement > largestMovement) largestMovement = movement;
129
145
  }
146
+ return largestMovement;
130
147
  };
148
+ /**
149
+ * Per-frame movement below which the grid counts as at rest, in pixels.
150
+ *
151
+ * Well under a device pixel, so the frame that crosses it is the last one that
152
+ * could have changed anything on screen.
153
+ */
154
+ const SETTLE_THRESHOLD_PX = .05;
131
155
  const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength = .12, lineColor, highlightColor, className, ...props }) => {
132
156
  const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME;
133
157
  const resolvedLineColor = lineColor ?? theme.lineColor;
@@ -139,6 +163,19 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
139
163
  active: false
140
164
  });
141
165
  const points = useRef([]);
166
+ /**
167
+ * Bumped on every pointer move. The loop records the value it rendered so it
168
+ * can tell "the pointer is somewhere over the page but has not moved" — which
169
+ * looks identical frame to frame — from a genuine change.
170
+ */
171
+ const pointerMoveCount = useRef(0);
172
+ /**
173
+ * Restarts the animation loop after it has parked itself.
174
+ *
175
+ * Assigned by the animation effect; the resize, colour and pointer effects
176
+ * call it so a change they make is picked up even when the grid was at rest.
177
+ */
178
+ const wakeLoop = useRef(() => void 0);
142
179
  const radiusRef = useRef(radius);
143
180
  const strengthRef = useRef(strength);
144
181
  const lineRgbRef = useRef(FALLBACK_RGB);
@@ -154,6 +191,7 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
154
191
  if (!svg) return;
155
192
  lineRgbRef.current = resolveColorToRgb(resolvedLineColor, svg);
156
193
  highlightRgbRef.current = resolveColorToRgb(resolvedHighlightColor, svg);
194
+ wakeLoop.current();
157
195
  }, [resolvedLineColor, resolvedHighlightColor]);
158
196
  useEffect(() => {
159
197
  const svg = svgRef.current;
@@ -175,6 +213,7 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
175
213
  newGrid.push(row);
176
214
  }
177
215
  points.current = newGrid;
216
+ wakeLoop.current();
178
217
  };
179
218
  resize();
180
219
  const resizeObserver = new ResizeObserver(resize);
@@ -191,9 +230,13 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
191
230
  y: event.clientY - rect.top,
192
231
  active: true
193
232
  };
233
+ pointerMoveCount.current++;
234
+ wakeLoop.current();
194
235
  };
195
236
  const handlePointerLeave = () => {
196
237
  mouse.current.active = false;
238
+ pointerMoveCount.current++;
239
+ wakeLoop.current();
197
240
  };
198
241
  window.addEventListener("pointermove", handlePointerMove, { passive: true });
199
242
  window.addEventListener("pointerleave", handlePointerLeave, { passive: true });
@@ -202,11 +245,39 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
202
245
  window.removeEventListener("pointerleave", handlePointerLeave);
203
246
  };
204
247
  }, []);
248
+ /**
249
+ * Runs the grid only while it has something to show.
250
+ *
251
+ * Each frame rewrites four attributes on every segment — on a full-viewport
252
+ * grid that is close to nine thousand `setAttribute` calls. Left running
253
+ * unconditionally it saturated the main thread of any page embedding the
254
+ * pattern, which starved React's transition lanes and made every other
255
+ * scroll-driven animation on the page advance in visible steps.
256
+ *
257
+ * So the loop parks itself whenever the grid is at rest and the pointer has
258
+ * not moved, and while the SVG is scrolled out of view. Anything that can
259
+ * change the picture — a pointer move, a resize, a colour change, the element
260
+ * coming back on screen — wakes it through {@link wakeLoop}.
261
+ */
205
262
  useEffect(() => {
206
263
  const svg = svgRef.current;
207
264
  if (!svg) return;
208
265
  const pathPool = [];
209
266
  let frame = 0;
267
+ let isRunning = false;
268
+ let isOnScreen = true;
269
+ /** Value of `pointerMoveCount` the last rendered frame was drawn for. */
270
+ let renderedPointerMoveCount = -1;
271
+ const start = () => {
272
+ if (isRunning || !isOnScreen) return;
273
+ isRunning = true;
274
+ frame = requestAnimationFrame(tick);
275
+ };
276
+ const stop = () => {
277
+ if (!isRunning) return;
278
+ isRunning = false;
279
+ cancelAnimationFrame(frame);
280
+ };
210
281
  const ensurePathCount = (count) => {
211
282
  while (pathPool.length < count) {
212
283
  const el = document.createElementNS("http://www.w3.org/2000/svg", "path");
@@ -219,9 +290,10 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
219
290
  const tick = () => {
220
291
  const grid = points.current;
221
292
  if (!grid.length) {
222
- frame = requestAnimationFrame(tick);
293
+ isRunning = false;
223
294
  return;
224
295
  }
296
+ const pointerMoveCountAtFrameStart = pointerMoveCount.current;
225
297
  const { x: mouseX, y: mouseY, active: mouseActive } = mouse.current;
226
298
  const currentRadius = radiusRef.current;
227
299
  const currentStrength = strengthRef.current;
@@ -229,7 +301,11 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
229
301
  const highlightRgb = highlightRgbRef.current;
230
302
  const baseOpacity = baseOpacityRef.current;
231
303
  const highlightOpacityBoost = highlightOpacityBoostRef.current;
232
- updateGridPoints(grid, mouseX, mouseY, mouseActive, currentRadius, currentStrength);
304
+ if (updateGridPoints(grid, mouseX, mouseY, mouseActive, currentRadius, currentStrength) < SETTLE_THRESHOLD_PX && renderedPointerMoveCount === pointerMoveCountAtFrameStart) {
305
+ isRunning = false;
306
+ return;
307
+ }
308
+ renderedPointerMoveCount = pointerMoveCountAtFrameStart;
233
309
  const rows = grid.length;
234
310
  const columns = grid[0].length;
235
311
  const totalSegments = rows * (columns - 1) + columns * (rows - 1);
@@ -257,9 +333,18 @@ const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength
257
333
  }
258
334
  frame = requestAnimationFrame(tick);
259
335
  };
260
- tick();
336
+ wakeLoop.current = start;
337
+ const visibilityObserver = new IntersectionObserver(([entry]) => {
338
+ isOnScreen = entry?.isIntersecting ?? true;
339
+ if (isOnScreen) start();
340
+ else stop();
341
+ });
342
+ visibilityObserver.observe(svg);
343
+ start();
261
344
  return () => {
262
- cancelAnimationFrame(frame);
345
+ wakeLoop.current = () => void 0;
346
+ visibilityObserver.disconnect();
347
+ stop();
263
348
  for (const el of pathPool) el.remove();
264
349
  };
265
350
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"GridDistortionPattern.mjs","names":[],"sources":["../../../../src/components/Pattern/GridDistortionPattern.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type SVGProps, useEffect, useRef } from 'react';\nimport { useIsDarkMode } from '../../hooks/useIsDarkMode';\n\n// ---------------------------------------------------------------------------\n// Theme configuration — tweak these to adjust the grid appearance globally\n// ---------------------------------------------------------------------------\n\ntype GridTheme = {\n /** Resting line color, used far from the pointer. */\n lineColor: string;\n /**\n * Color the lines fade toward as the pointer approaches. To avoid lines\n * appearing to \"dim into the background\" near the pointer, this should be\n * brighter (higher luminance) than {@link GridTheme.lineColor} in dark mode\n * and clearly contrasting in light mode.\n */\n highlightColor: string;\n /** Stroke opacity far from the pointer — keeps the whole grid visible. */\n baseOpacity: number;\n /** Extra opacity added at the pointer's center on top of {@link GridTheme.baseOpacity}. */\n highlightOpacityBoost: number;\n};\n\n/**\n * Light mode: a visible (but subtle) gray line on white. The base opacity is\n * raised and the highlight boost lowered so the grid no longer fades to nothing\n * far from the pointer while spiking to a harsh accent underneath it.\n */\nconst LIGHT_THEME: GridTheme = {\n lineColor: '#a8a8a8',\n highlightColor: '#969696',\n baseOpacity: 0.3,\n highlightOpacityBoost: 0.3,\n};\n\n/**\n * Dark mode: a mid-gray line that brightens toward a light purple near the\n * pointer (brighter than the resting line, so the hovered segment lights up\n * instead of darkening into the black background).\n */\nconst DARK_THEME: GridTheme = {\n lineColor: '#4d4d4d',\n highlightColor: '#5c5c5c',\n baseOpacity: 0.2,\n highlightOpacityBoost: 0.2,\n};\n\n/**\n * Falloff exponent for the highlight. A gentler curve (2 = quadratic) spreads\n * the highlight over a wider area than a cubic one, smoothing the transition\n * between the resting line and the accent under the pointer.\n */\nconst HIGHLIGHT_FALLOFF_EXPONENT = 2;\n\n// ---------------------------------------------------------------------------\n// Pure helpers — defined once at module level, never recreated\n// ---------------------------------------------------------------------------\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const numericValue = parseInt(hex.replace('#', ''), 16);\n return [\n (numericValue >> 16) & 255,\n (numericValue >> 8) & 255,\n numericValue & 255,\n ];\n};\n\n/** Neutral gray used when a color string can't be parsed into RGB. */\nconst FALLBACK_RGB: [number, number, number] = [136, 136, 136];\n\n/**\n * Resolves any CSS color string — `#rrggbb`, `rgb()/hsl()`, a named color, or a\n * `var(--token)` reference — into an `[r, g, b]` triple.\n *\n * 6-digit hex is parsed directly. Everything else is resolved by the browser\n * via a throwaway probe element mounted inside `contextElement`, so custom\n * properties resolve against the correct cascade. Falls back to a neutral gray\n * when the value can't be parsed, so the grid never emits invalid\n * `rgb(NaN, …)` strokes (which browsers render as solid black).\n */\nconst resolveColorToRgb = (\n color: string,\n contextElement: Element\n): [number, number, number] => {\n const trimmed = color.trim();\n\n // Fast path: 6-digit hex needs no DOM access.\n if (/^#[0-9a-f]{6}$/i.test(trimmed)) {\n return hexToRgb(trimmed);\n }\n\n const probe = document.createElement('span');\n probe.style.color = trimmed;\n probe.style.display = 'none';\n contextElement.appendChild(probe);\n const computedColor = getComputedStyle(probe).color;\n probe.remove();\n\n const channels = computedColor.match(/-?\\d+(?:\\.\\d+)?/g);\n if (channels && channels.length >= 3) {\n return [Number(channels[0]), Number(channels[1]), Number(channels[2])];\n }\n\n return FALLBACK_RGB;\n};\n\ntype Point = { baseX: number; baseY: number; x: number; y: number };\n\nconst buildSegmentAttrs = (\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n mouseX: number,\n mouseY: number,\n mouseActive: boolean,\n radius: number,\n strength: number,\n lineRgb: [number, number, number],\n highlightRgb: [number, number, number],\n baseOpacity: number,\n highlightOpacityBoost: number\n): {\n d: string;\n stroke: string;\n strokeOpacity: number;\n strokeWidth: number;\n} => {\n let red: number;\n let green: number;\n let blue: number;\n let opacity: number;\n let strokeWidth: number;\n\n if (!mouseActive) {\n [red, green, blue] = lineRgb;\n opacity = baseOpacity;\n strokeWidth = 0.8;\n } else {\n const midX = (startX + endX) / 2;\n const midY = (startY + endY) / 2;\n const deltaX = mouseX - midX;\n const deltaY = mouseY - midY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const distanceRatio = Math.max(0, 1 - distance / radius);\n const easing = distanceRatio ** HIGHLIGHT_FALLOFF_EXPONENT;\n red = Math.round(lineRgb[0] + (highlightRgb[0] - lineRgb[0]) * easing);\n green = Math.round(lineRgb[1] + (highlightRgb[1] - lineRgb[1]) * easing);\n blue = Math.round(lineRgb[2] + (highlightRgb[2] - lineRgb[2]) * easing);\n opacity = baseOpacity + easing * highlightOpacityBoost;\n strokeWidth = 0.8 + easing * 1.2;\n void strength; // currently unused in segment coloring, kept for API compatibility\n }\n\n return {\n d: `M ${startX} ${startY} L ${endX} ${endY}`,\n stroke: `rgb(${red},${green},${blue})`,\n strokeOpacity: opacity,\n strokeWidth,\n };\n};\n\nconst updateGridPoints = (\n grid: Point[][],\n mouseX: number,\n mouseY: number,\n mouseActive: boolean,\n radius: number,\n strength: number\n): void => {\n const rows = grid.length;\n const columns = grid[0]!.length;\n\n for (let rowIndex = 0; rowIndex < rows; rowIndex++) {\n for (let columnIndex = 0; columnIndex < columns; columnIndex++) {\n const point = grid[rowIndex]![columnIndex]!;\n if (!mouseActive) {\n point.x += (point.baseX - point.x) * 0.08;\n point.y += (point.baseY - point.y) * 0.08;\n continue;\n }\n const deltaX = mouseX - point.baseX;\n const deltaY = mouseY - point.baseY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n if (distance < radius && distance > 0) {\n const force = (Math.cos((distance / radius) * Math.PI) + 1) / 2;\n const displacement = force * strength * radius * 0.3;\n point.x +=\n (point.baseX + (deltaX / distance) * displacement - point.x) * 0.12;\n point.y +=\n (point.baseY + (deltaY / distance) * displacement - point.y) * 0.12;\n } else {\n point.x += (point.baseX - point.x) * 0.08;\n point.y += (point.baseY - point.y) * 0.08;\n }\n }\n }\n};\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\ntype GridDistortionPatternProps = {\n width?: number;\n height?: number;\n radius?: number;\n strength?: number;\n lineColor?: string;\n highlightColor?: string;\n} & SVGProps<SVGSVGElement>;\n\nexport const GridDistortionPattern: FC<GridDistortionPatternProps> = ({\n width = 70,\n height = 70,\n radius = 280,\n strength = 0.12,\n lineColor,\n highlightColor,\n className,\n ...props\n}) => {\n const isDarkMode = useIsDarkMode();\n const theme = isDarkMode ? DARK_THEME : LIGHT_THEME;\n\n // Explicit props win; otherwise fall back to the active theme's defaults.\n const resolvedLineColor = lineColor ?? theme.lineColor;\n const resolvedHighlightColor = highlightColor ?? theme.highlightColor;\n\n const svgRef = useRef<SVGSVGElement>(null);\n const mouse = useRef({ x: -9999, y: -9999, active: false });\n const points = useRef<Point[][]>([]);\n\n // Stable refs so the animation loop always sees the latest prop values\n // without needing to restart itself.\n const radiusRef = useRef(radius);\n const strengthRef = useRef(strength);\n // Initialized to safe placeholders; the real (possibly CSS-variable-based)\n // colors are resolved against the DOM in the effect below.\n const lineRgbRef = useRef<[number, number, number]>(FALLBACK_RGB);\n const highlightRgbRef = useRef<[number, number, number]>(FALLBACK_RGB);\n const baseOpacityRef = useRef(theme.baseOpacity);\n const highlightOpacityBoostRef = useRef(theme.highlightOpacityBoost);\n\n // Keep numeric refs in sync with props on each render (cheap assignments).\n radiusRef.current = radius;\n strengthRef.current = strength;\n baseOpacityRef.current = theme.baseOpacity;\n highlightOpacityBoostRef.current = theme.highlightOpacityBoost;\n\n // Resolve colors once mounted — getComputedStyle is required so that\n // `var(--token)` highlight/line colors resolve against the live cascade.\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n lineRgbRef.current = resolveColorToRgb(resolvedLineColor, svg);\n highlightRgbRef.current = resolveColorToRgb(resolvedHighlightColor, svg);\n }, [resolvedLineColor, resolvedHighlightColor]);\n\n // ---------------------------------------------------------------------------\n // Resize — rebuild the point grid and resize the SVG viewBox\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const resize = () => {\n const rect = svg.getBoundingClientRect();\n svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`);\n\n const columns = Math.ceil(rect.width / width) + 2;\n const rows = Math.ceil(rect.height / height) + 2;\n const newGrid: Point[][] = [];\n\n for (let rowIndex = -1; rowIndex <= rows; rowIndex++) {\n const row: Point[] = [];\n for (let columnIndex = -1; columnIndex <= columns; columnIndex++) {\n row.push({\n baseX: columnIndex * width,\n baseY: rowIndex * height,\n x: columnIndex * width,\n y: rowIndex * height,\n });\n }\n newGrid.push(row);\n }\n points.current = newGrid;\n };\n\n resize();\n const resizeObserver = new ResizeObserver(resize);\n resizeObserver.observe(svg);\n return () => resizeObserver.disconnect();\n }, [width, height]);\n\n // ---------------------------------------------------------------------------\n // Pointer tracking — passive listeners, no React state involved\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = svg.getBoundingClientRect();\n mouse.current = {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n active: true,\n };\n };\n const handlePointerLeave = () => {\n mouse.current.active = false;\n };\n\n window.addEventListener('pointermove', handlePointerMove, {\n passive: true,\n });\n window.addEventListener('pointerleave', handlePointerLeave, {\n passive: true,\n });\n return () => {\n window.removeEventListener('pointermove', handlePointerMove);\n window.removeEventListener('pointerleave', handlePointerLeave);\n };\n }, []);\n\n // ---------------------------------------------------------------------------\n // Animation loop — writes directly to SVG DOM, no React state/re-renders\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n // We maintain a pool of <path> elements and reuse them each frame\n // to avoid GC pressure from creating/deleting DOM nodes every tick.\n const pathPool: SVGPathElement[] = [];\n let frame = 0;\n\n const ensurePathCount = (count: number) => {\n while (pathPool.length < count) {\n const el = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'path'\n );\n el.setAttribute('fill', 'none');\n svg.appendChild(el);\n pathPool.push(el);\n }\n // Hide surplus paths\n for (let i = count; i < pathPool.length; i++) {\n pathPool[i]!.setAttribute('d', '');\n }\n };\n\n const tick = () => {\n const grid = points.current;\n if (!grid.length) {\n frame = requestAnimationFrame(tick);\n return;\n }\n\n const { x: mouseX, y: mouseY, active: mouseActive } = mouse.current;\n const currentRadius = radiusRef.current;\n const currentStrength = strengthRef.current;\n const lineRgb = lineRgbRef.current;\n const highlightRgb = highlightRgbRef.current;\n const baseOpacity = baseOpacityRef.current;\n const highlightOpacityBoost = highlightOpacityBoostRef.current;\n\n // Update point positions\n updateGridPoints(\n grid,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength\n );\n\n const rows = grid.length;\n const columns = grid[0]!.length;\n\n // Count segments up front so we can size the pool\n const horizontalCount = rows * (columns - 1);\n const verticalCount = columns * (rows - 1);\n const totalSegments = horizontalCount + verticalCount;\n ensurePathCount(totalSegments);\n\n let pathIndex = 0;\n\n // Horizontal segments\n for (let rowIndex = 0; rowIndex < rows; rowIndex++) {\n for (let columnIndex = 0; columnIndex < columns - 1; columnIndex++) {\n const p0 = grid[rowIndex]![columnIndex]!;\n const p1 = grid[rowIndex]![columnIndex + 1]!;\n const attrs = buildSegmentAttrs(\n p0.x,\n p0.y,\n p1.x,\n p1.y,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength,\n lineRgb,\n highlightRgb,\n baseOpacity,\n highlightOpacityBoost\n );\n const el = pathPool[pathIndex++]!;\n el.setAttribute('d', attrs.d);\n el.setAttribute('stroke', attrs.stroke);\n el.setAttribute('stroke-opacity', String(attrs.strokeOpacity));\n el.setAttribute('stroke-width', String(attrs.strokeWidth));\n }\n }\n\n // Vertical segments\n for (let columnIndex = 0; columnIndex < columns; columnIndex++) {\n for (let rowIndex = 0; rowIndex < rows - 1; rowIndex++) {\n const p0 = grid[rowIndex]![columnIndex]!;\n const p1 = grid[rowIndex + 1]![columnIndex]!;\n const attrs = buildSegmentAttrs(\n p0.x,\n p0.y,\n p1.x,\n p1.y,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength,\n lineRgb,\n highlightRgb,\n baseOpacity,\n highlightOpacityBoost\n );\n const el = pathPool[pathIndex++]!;\n el.setAttribute('d', attrs.d);\n el.setAttribute('stroke', attrs.stroke);\n el.setAttribute('stroke-opacity', String(attrs.strokeOpacity));\n el.setAttribute('stroke-width', String(attrs.strokeWidth));\n }\n }\n\n frame = requestAnimationFrame(tick);\n };\n\n tick();\n\n return () => {\n cancelAnimationFrame(frame);\n // Clean up path elements we injected\n for (const el of pathPool) {\n el.remove();\n }\n };\n }, []); // no dependencies — reads everything via refs\n\n return (\n <svg\n ref={svgRef}\n aria-hidden=\"true\"\n className={cn(\n 'pointer-events-none absolute inset-0 size-full',\n className\n )}\n {...props}\n />\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"GridDistortionPattern.mjs","names":[],"sources":["../../../../src/components/Pattern/GridDistortionPattern.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type SVGProps, useEffect, useRef } from 'react';\nimport { useIsDarkMode } from '../../hooks/useIsDarkMode';\n\n// ---------------------------------------------------------------------------\n// Theme configuration — tweak these to adjust the grid appearance globally\n// ---------------------------------------------------------------------------\n\ntype GridTheme = {\n /** Resting line color, used far from the pointer. */\n lineColor: string;\n /**\n * Color the lines fade toward as the pointer approaches. To avoid lines\n * appearing to \"dim into the background\" near the pointer, this should be\n * brighter (higher luminance) than {@link GridTheme.lineColor} in dark mode\n * and clearly contrasting in light mode.\n */\n highlightColor: string;\n /** Stroke opacity far from the pointer — keeps the whole grid visible. */\n baseOpacity: number;\n /** Extra opacity added at the pointer's center on top of {@link GridTheme.baseOpacity}. */\n highlightOpacityBoost: number;\n};\n\n/**\n * Light mode: a visible (but subtle) gray line on white. The base opacity is\n * raised and the highlight boost lowered so the grid no longer fades to nothing\n * far from the pointer while spiking to a harsh accent underneath it.\n */\nconst LIGHT_THEME: GridTheme = {\n lineColor: '#a8a8a8',\n highlightColor: '#969696',\n baseOpacity: 0.3,\n highlightOpacityBoost: 0.3,\n};\n\n/**\n * Dark mode: a mid-gray line that brightens toward a light purple near the\n * pointer (brighter than the resting line, so the hovered segment lights up\n * instead of darkening into the black background).\n */\nconst DARK_THEME: GridTheme = {\n lineColor: '#4d4d4d',\n highlightColor: '#5c5c5c',\n baseOpacity: 0.2,\n highlightOpacityBoost: 0.2,\n};\n\n/**\n * Falloff exponent for the highlight. A gentler curve (2 = quadratic) spreads\n * the highlight over a wider area than a cubic one, smoothing the transition\n * between the resting line and the accent under the pointer.\n */\nconst HIGHLIGHT_FALLOFF_EXPONENT = 2;\n\n// ---------------------------------------------------------------------------\n// Pure helpers — defined once at module level, never recreated\n// ---------------------------------------------------------------------------\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const numericValue = parseInt(hex.replace('#', ''), 16);\n return [\n (numericValue >> 16) & 255,\n (numericValue >> 8) & 255,\n numericValue & 255,\n ];\n};\n\n/** Neutral gray used when a color string can't be parsed into RGB. */\nconst FALLBACK_RGB: [number, number, number] = [136, 136, 136];\n\n/**\n * Resolves any CSS color string — `#rrggbb`, `rgb()/hsl()`, a named color, or a\n * `var(--token)` reference — into an `[r, g, b]` triple.\n *\n * 6-digit hex is parsed directly. Everything else is resolved by the browser\n * via a throwaway probe element mounted inside `contextElement`, so custom\n * properties resolve against the correct cascade. Falls back to a neutral gray\n * when the value can't be parsed, so the grid never emits invalid\n * `rgb(NaN, …)` strokes (which browsers render as solid black).\n */\nconst resolveColorToRgb = (\n color: string,\n contextElement: Element\n): [number, number, number] => {\n const trimmed = color.trim();\n\n // Fast path: 6-digit hex needs no DOM access.\n if (/^#[0-9a-f]{6}$/i.test(trimmed)) {\n return hexToRgb(trimmed);\n }\n\n const probe = document.createElement('span');\n probe.style.color = trimmed;\n probe.style.display = 'none';\n contextElement.appendChild(probe);\n const computedColor = getComputedStyle(probe).color;\n probe.remove();\n\n const channels = computedColor.match(/-?\\d+(?:\\.\\d+)?/g);\n if (channels && channels.length >= 3) {\n return [Number(channels[0]), Number(channels[1]), Number(channels[2])];\n }\n\n return FALLBACK_RGB;\n};\n\ntype Point = { baseX: number; baseY: number; x: number; y: number };\n\nconst buildSegmentAttrs = (\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n mouseX: number,\n mouseY: number,\n mouseActive: boolean,\n radius: number,\n strength: number,\n lineRgb: [number, number, number],\n highlightRgb: [number, number, number],\n baseOpacity: number,\n highlightOpacityBoost: number\n): {\n d: string;\n stroke: string;\n strokeOpacity: number;\n strokeWidth: number;\n} => {\n let red: number;\n let green: number;\n let blue: number;\n let opacity: number;\n let strokeWidth: number;\n\n if (!mouseActive) {\n [red, green, blue] = lineRgb;\n opacity = baseOpacity;\n strokeWidth = 0.8;\n } else {\n const midX = (startX + endX) / 2;\n const midY = (startY + endY) / 2;\n const deltaX = mouseX - midX;\n const deltaY = mouseY - midY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const distanceRatio = Math.max(0, 1 - distance / radius);\n const easing = distanceRatio ** HIGHLIGHT_FALLOFF_EXPONENT;\n red = Math.round(lineRgb[0] + (highlightRgb[0] - lineRgb[0]) * easing);\n green = Math.round(lineRgb[1] + (highlightRgb[1] - lineRgb[1]) * easing);\n blue = Math.round(lineRgb[2] + (highlightRgb[2] - lineRgb[2]) * easing);\n opacity = baseOpacity + easing * highlightOpacityBoost;\n strokeWidth = 0.8 + easing * 1.2;\n void strength; // currently unused in segment coloring, kept for API compatibility\n }\n\n return {\n d: `M ${startX} ${startY} L ${endX} ${endY}`,\n stroke: `rgb(${red},${green},${blue})`,\n strokeOpacity: opacity,\n strokeWidth,\n };\n};\n\n/**\n * Advances every point one step toward its target and reports how far the\n * furthest one travelled.\n *\n * The caller uses that distance to decide whether another frame is worth\n * running: the relaxation is exponential, so once it drops below\n * {@link SETTLE_THRESHOLD_PX} the grid is visually at rest and redrawing it\n * only burns the main thread.\n *\n * @returns Largest per-axis distance any point moved this step, in pixels.\n */\nconst updateGridPoints = (\n grid: Point[][],\n mouseX: number,\n mouseY: number,\n mouseActive: boolean,\n radius: number,\n strength: number\n): number => {\n const rows = grid.length;\n const columns = grid[0]!.length;\n let largestMovement = 0;\n\n for (let rowIndex = 0; rowIndex < rows; rowIndex++) {\n for (let columnIndex = 0; columnIndex < columns; columnIndex++) {\n const point = grid[rowIndex]![columnIndex]!;\n const previousX = point.x;\n const previousY = point.y;\n\n if (!mouseActive) {\n point.x += (point.baseX - point.x) * 0.08;\n point.y += (point.baseY - point.y) * 0.08;\n } else {\n const deltaX = mouseX - point.baseX;\n const deltaY = mouseY - point.baseY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n if (distance < radius && distance > 0) {\n const force = (Math.cos((distance / radius) * Math.PI) + 1) / 2;\n const displacement = force * strength * radius * 0.3;\n point.x +=\n (point.baseX + (deltaX / distance) * displacement - point.x) * 0.12;\n point.y +=\n (point.baseY + (deltaY / distance) * displacement - point.y) * 0.12;\n } else {\n point.x += (point.baseX - point.x) * 0.08;\n point.y += (point.baseY - point.y) * 0.08;\n }\n }\n\n const movement = Math.max(\n Math.abs(point.x - previousX),\n Math.abs(point.y - previousY)\n );\n if (movement > largestMovement) largestMovement = movement;\n }\n }\n\n return largestMovement;\n};\n\n/**\n * Per-frame movement below which the grid counts as at rest, in pixels.\n *\n * Well under a device pixel, so the frame that crosses it is the last one that\n * could have changed anything on screen.\n */\nconst SETTLE_THRESHOLD_PX = 0.05;\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\ntype GridDistortionPatternProps = {\n width?: number;\n height?: number;\n radius?: number;\n strength?: number;\n lineColor?: string;\n highlightColor?: string;\n} & SVGProps<SVGSVGElement>;\n\nexport const GridDistortionPattern: FC<GridDistortionPatternProps> = ({\n width = 70,\n height = 70,\n radius = 280,\n strength = 0.12,\n lineColor,\n highlightColor,\n className,\n ...props\n}) => {\n const isDarkMode = useIsDarkMode();\n const theme = isDarkMode ? DARK_THEME : LIGHT_THEME;\n\n // Explicit props win; otherwise fall back to the active theme's defaults.\n const resolvedLineColor = lineColor ?? theme.lineColor;\n const resolvedHighlightColor = highlightColor ?? theme.highlightColor;\n\n const svgRef = useRef<SVGSVGElement>(null);\n const mouse = useRef({ x: -9999, y: -9999, active: false });\n const points = useRef<Point[][]>([]);\n /**\n * Bumped on every pointer move. The loop records the value it rendered so it\n * can tell \"the pointer is somewhere over the page but has not moved\" — which\n * looks identical frame to frame — from a genuine change.\n */\n const pointerMoveCount = useRef(0);\n /**\n * Restarts the animation loop after it has parked itself.\n *\n * Assigned by the animation effect; the resize, colour and pointer effects\n * call it so a change they make is picked up even when the grid was at rest.\n */\n const wakeLoop = useRef<() => void>(() => undefined);\n\n // Stable refs so the animation loop always sees the latest prop values\n // without needing to restart itself.\n const radiusRef = useRef(radius);\n const strengthRef = useRef(strength);\n // Initialized to safe placeholders; the real (possibly CSS-variable-based)\n // colors are resolved against the DOM in the effect below.\n const lineRgbRef = useRef<[number, number, number]>(FALLBACK_RGB);\n const highlightRgbRef = useRef<[number, number, number]>(FALLBACK_RGB);\n const baseOpacityRef = useRef(theme.baseOpacity);\n const highlightOpacityBoostRef = useRef(theme.highlightOpacityBoost);\n\n // Keep numeric refs in sync with props on each render (cheap assignments).\n radiusRef.current = radius;\n strengthRef.current = strength;\n baseOpacityRef.current = theme.baseOpacity;\n highlightOpacityBoostRef.current = theme.highlightOpacityBoost;\n\n // Resolve colors once mounted — getComputedStyle is required so that\n // `var(--token)` highlight/line colors resolve against the live cascade.\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n lineRgbRef.current = resolveColorToRgb(resolvedLineColor, svg);\n highlightRgbRef.current = resolveColorToRgb(resolvedHighlightColor, svg);\n wakeLoop.current();\n }, [resolvedLineColor, resolvedHighlightColor]);\n\n // ---------------------------------------------------------------------------\n // Resize — rebuild the point grid and resize the SVG viewBox\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const resize = () => {\n const rect = svg.getBoundingClientRect();\n svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`);\n\n const columns = Math.ceil(rect.width / width) + 2;\n const rows = Math.ceil(rect.height / height) + 2;\n const newGrid: Point[][] = [];\n\n for (let rowIndex = -1; rowIndex <= rows; rowIndex++) {\n const row: Point[] = [];\n for (let columnIndex = -1; columnIndex <= columns; columnIndex++) {\n row.push({\n baseX: columnIndex * width,\n baseY: rowIndex * height,\n x: columnIndex * width,\n y: rowIndex * height,\n });\n }\n newGrid.push(row);\n }\n points.current = newGrid;\n wakeLoop.current();\n };\n\n resize();\n const resizeObserver = new ResizeObserver(resize);\n resizeObserver.observe(svg);\n return () => resizeObserver.disconnect();\n }, [width, height]);\n\n // ---------------------------------------------------------------------------\n // Pointer tracking — passive listeners, no React state involved\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = svg.getBoundingClientRect();\n mouse.current = {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n active: true,\n };\n pointerMoveCount.current++;\n wakeLoop.current();\n };\n const handlePointerLeave = () => {\n mouse.current.active = false;\n pointerMoveCount.current++;\n wakeLoop.current();\n };\n\n window.addEventListener('pointermove', handlePointerMove, {\n passive: true,\n });\n window.addEventListener('pointerleave', handlePointerLeave, {\n passive: true,\n });\n return () => {\n window.removeEventListener('pointermove', handlePointerMove);\n window.removeEventListener('pointerleave', handlePointerLeave);\n };\n }, []);\n\n // ---------------------------------------------------------------------------\n // Animation loop — writes directly to SVG DOM, no React state/re-renders\n // ---------------------------------------------------------------------------\n /**\n * Runs the grid only while it has something to show.\n *\n * Each frame rewrites four attributes on every segment — on a full-viewport\n * grid that is close to nine thousand `setAttribute` calls. Left running\n * unconditionally it saturated the main thread of any page embedding the\n * pattern, which starved React's transition lanes and made every other\n * scroll-driven animation on the page advance in visible steps.\n *\n * So the loop parks itself whenever the grid is at rest and the pointer has\n * not moved, and while the SVG is scrolled out of view. Anything that can\n * change the picture — a pointer move, a resize, a colour change, the element\n * coming back on screen — wakes it through {@link wakeLoop}.\n */\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n // We maintain a pool of <path> elements and reuse them each frame\n // to avoid GC pressure from creating/deleting DOM nodes every tick.\n const pathPool: SVGPathElement[] = [];\n let frame = 0;\n let isRunning = false;\n let isOnScreen = true;\n /** Value of `pointerMoveCount` the last rendered frame was drawn for. */\n let renderedPointerMoveCount = -1;\n\n const start = () => {\n if (isRunning || !isOnScreen) return;\n isRunning = true;\n frame = requestAnimationFrame(tick);\n };\n\n const stop = () => {\n if (!isRunning) return;\n isRunning = false;\n cancelAnimationFrame(frame);\n };\n\n const ensurePathCount = (count: number) => {\n while (pathPool.length < count) {\n const el = document.createElementNS(\n 'http://www.w3.org/2000/svg',\n 'path'\n );\n el.setAttribute('fill', 'none');\n svg.appendChild(el);\n pathPool.push(el);\n }\n // Hide surplus paths\n for (let i = count; i < pathPool.length; i++) {\n pathPool[i]!.setAttribute('d', '');\n }\n };\n\n const tick = () => {\n const grid = points.current;\n if (!grid.length) {\n // No grid yet — the resize effect will wake us once it has built one.\n isRunning = false;\n return;\n }\n\n const pointerMoveCountAtFrameStart = pointerMoveCount.current;\n\n const { x: mouseX, y: mouseY, active: mouseActive } = mouse.current;\n const currentRadius = radiusRef.current;\n const currentStrength = strengthRef.current;\n const lineRgb = lineRgbRef.current;\n const highlightRgb = highlightRgbRef.current;\n const baseOpacity = baseOpacityRef.current;\n const highlightOpacityBoost = highlightOpacityBoostRef.current;\n\n // Update point positions\n const largestMovement = updateGridPoints(\n grid,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength\n );\n\n // Nothing moved and the pointer is where it already was, so this frame\n // would repaint the picture already on screen.\n if (\n largestMovement < SETTLE_THRESHOLD_PX &&\n renderedPointerMoveCount === pointerMoveCountAtFrameStart\n ) {\n isRunning = false;\n return;\n }\n renderedPointerMoveCount = pointerMoveCountAtFrameStart;\n\n const rows = grid.length;\n const columns = grid[0]!.length;\n\n // Count segments up front so we can size the pool\n const horizontalCount = rows * (columns - 1);\n const verticalCount = columns * (rows - 1);\n const totalSegments = horizontalCount + verticalCount;\n ensurePathCount(totalSegments);\n\n let pathIndex = 0;\n\n // Horizontal segments\n for (let rowIndex = 0; rowIndex < rows; rowIndex++) {\n for (let columnIndex = 0; columnIndex < columns - 1; columnIndex++) {\n const p0 = grid[rowIndex]![columnIndex]!;\n const p1 = grid[rowIndex]![columnIndex + 1]!;\n const attrs = buildSegmentAttrs(\n p0.x,\n p0.y,\n p1.x,\n p1.y,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength,\n lineRgb,\n highlightRgb,\n baseOpacity,\n highlightOpacityBoost\n );\n const el = pathPool[pathIndex++]!;\n el.setAttribute('d', attrs.d);\n el.setAttribute('stroke', attrs.stroke);\n el.setAttribute('stroke-opacity', String(attrs.strokeOpacity));\n el.setAttribute('stroke-width', String(attrs.strokeWidth));\n }\n }\n\n // Vertical segments\n for (let columnIndex = 0; columnIndex < columns; columnIndex++) {\n for (let rowIndex = 0; rowIndex < rows - 1; rowIndex++) {\n const p0 = grid[rowIndex]![columnIndex]!;\n const p1 = grid[rowIndex + 1]![columnIndex]!;\n const attrs = buildSegmentAttrs(\n p0.x,\n p0.y,\n p1.x,\n p1.y,\n mouseX,\n mouseY,\n mouseActive,\n currentRadius,\n currentStrength,\n lineRgb,\n highlightRgb,\n baseOpacity,\n highlightOpacityBoost\n );\n const el = pathPool[pathIndex++]!;\n el.setAttribute('d', attrs.d);\n el.setAttribute('stroke', attrs.stroke);\n el.setAttribute('stroke-opacity', String(attrs.strokeOpacity));\n el.setAttribute('stroke-width', String(attrs.strokeWidth));\n }\n }\n\n frame = requestAnimationFrame(tick);\n };\n\n wakeLoop.current = start;\n\n // Off-screen the grid cannot be seen, so neither the relaxation nor the\n // redraw is worth a frame.\n const visibilityObserver = new IntersectionObserver(([entry]) => {\n isOnScreen = entry?.isIntersecting ?? true;\n if (isOnScreen) start();\n else stop();\n });\n visibilityObserver.observe(svg);\n\n start();\n\n return () => {\n wakeLoop.current = () => undefined;\n visibilityObserver.disconnect();\n stop();\n // Clean up path elements we injected\n for (const el of pathPool) {\n el.remove();\n }\n };\n }, []); // no dependencies — reads everything via refs\n\n return (\n <svg\n ref={svgRef}\n aria-hidden=\"true\"\n className={cn(\n 'pointer-events-none absolute inset-0 size-full',\n className\n )}\n {...props}\n />\n );\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Detail as Detail$1, PopoverStatic } from "./static.mjs";
3
+ import { Detail as Detail$1, PopoverStatic, usePopoverIds } from "./static.mjs";
4
4
  import { useLayoutEffect, useRef, useState } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
 
@@ -35,6 +35,7 @@ const PopoverComponent = (props) => {
35
35
  */
36
36
  const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
37
37
  const popoverRef = useRef(null);
38
+ const { triggerId } = usePopoverIds(props.identifier);
38
39
  const [computedXAlign, setComputedXAlign] = useState(xAlign);
39
40
  const [computedYAlign, setComputedYAlign] = useState(yAlign);
40
41
  const [maxWidth, setMaxWidth] = useState(void 0);
@@ -42,7 +43,7 @@ const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
42
43
  const adjustPosition = () => {
43
44
  if (!popoverRef.current) return;
44
45
  const popoverElement = popoverRef.current;
45
- const triggerElement = document.getElementById(`unrollable-panel-button-${props.identifier}`);
46
+ const triggerElement = document.getElementById(triggerId);
46
47
  if (!triggerElement) return;
47
48
  const triggerRect = triggerElement.getBoundingClientRect();
48
49
  const viewportWidth = window.innerWidth;
@@ -84,7 +85,7 @@ const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
84
85
  });
85
86
  };
86
87
  const timeoutId = setTimeout(adjustPosition, 0);
87
- const triggerElement = document.getElementById(`unrollable-panel-button-${props.identifier}`);
88
+ const triggerElement = document.getElementById(triggerId);
88
89
  if (triggerElement) {
89
90
  triggerElement.addEventListener("mouseenter", adjustPosition);
90
91
  triggerElement.addEventListener("focusin", adjustPosition);
@@ -106,7 +107,7 @@ const Detail = ({ xAlign = "start", yAlign = "below", ...props }) => {
106
107
  window.removeEventListener("scroll", adjustPosition, true);
107
108
  };
108
109
  }, [
109
- props.identifier,
110
+ triggerId,
110
111
  xAlign,
111
112
  yAlign
112
113
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n} from './static';\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Listens to window resize and scroll events\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const adjustPosition = () => {\n if (!popoverRef.current) return;\n\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(\n `unrollable-panel-button-${props.identifier}`\n );\n\n if (!triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const gap = 16; // 1rem gap\n const padding = 16; // Additional padding from viewport edges\n\n // Calculate maximum width based on viewport and trigger position\n const maxWidthFromLeft = viewportWidth - triggerRect.left - padding;\n const maxWidthFromRight = triggerRect.right - padding;\n\n // Use the larger space to ensure popover can fit\n const absoluteMaxWidth = Math.max(maxWidthFromLeft, maxWidthFromRight);\n\n setMaxWidth(absoluteMaxWidth);\n\n // Force a layout calculation by temporarily making visible if needed\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // Small delay to ensure max-width is applied and content reflows\n requestAnimationFrame(() => {\n const popoverRect = popoverElement.getBoundingClientRect();\n\n // Restore invisible state if it was invisible\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - gap;\n const spaceAbove = triggerRect.top - gap;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - padding;\n const spaceLeft = triggerRect.right - padding;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n // Adjust position with a slight delay to ensure DOM is ready\n const timeoutId = setTimeout(adjustPosition, 0);\n\n // Listen to mouse enter on the trigger to recalculate\n const triggerElement = document.getElementById(\n `unrollable-panel-button-${props.identifier}`\n );\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', adjustPosition);\n triggerElement.addEventListener('focusin', adjustPosition);\n }\n\n // Use ResizeObserver to detect popover content size changes\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', adjustPosition);\n window.addEventListener('scroll', adjustPosition, true);\n\n return () => {\n clearTimeout(timeoutId);\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', adjustPosition);\n triggerElement.removeEventListener('focusin', adjustPosition);\n }\n resizeObserver.disconnect();\n window.removeEventListener('resize', adjustPosition);\n window.removeEventListener('scroll', adjustPosition, true);\n };\n }, [props.identifier, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":"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"}
1
+ {"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n usePopoverIds,\n} from './static';\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Listens to window resize and scroll events\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const { triggerId } = usePopoverIds(props.identifier);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const adjustPosition = () => {\n if (!popoverRef.current) return;\n\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(triggerId);\n\n if (!triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const gap = 16; // 1rem gap\n const padding = 16; // Additional padding from viewport edges\n\n // Calculate maximum width based on viewport and trigger position\n const maxWidthFromLeft = viewportWidth - triggerRect.left - padding;\n const maxWidthFromRight = triggerRect.right - padding;\n\n // Use the larger space to ensure popover can fit\n const absoluteMaxWidth = Math.max(maxWidthFromLeft, maxWidthFromRight);\n\n setMaxWidth(absoluteMaxWidth);\n\n // Force a layout calculation by temporarily making visible if needed\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // Small delay to ensure max-width is applied and content reflows\n requestAnimationFrame(() => {\n const popoverRect = popoverElement.getBoundingClientRect();\n\n // Restore invisible state if it was invisible\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - gap;\n const spaceAbove = triggerRect.top - gap;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - padding;\n const spaceLeft = triggerRect.right - padding;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n // Adjust position with a slight delay to ensure DOM is ready\n const timeoutId = setTimeout(adjustPosition, 0);\n\n // Listen to mouse enter on the trigger to recalculate\n const triggerElement = document.getElementById(triggerId);\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', adjustPosition);\n triggerElement.addEventListener('focusin', adjustPosition);\n }\n\n // Use ResizeObserver to detect popover content size changes\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', adjustPosition);\n window.addEventListener('scroll', adjustPosition, true);\n\n return () => {\n clearTimeout(timeoutId);\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', adjustPosition);\n triggerElement.removeEventListener('focusin', adjustPosition);\n }\n resizeObserver.disconnect();\n window.removeEventListener('resize', adjustPosition);\n window.removeEventListener('scroll', adjustPosition, true);\n };\n }, [triggerId, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Detail, PopoverStatic } from "./static.mjs";
1
+ import { Detail, PopoverStatic, usePopoverIds } from "./static.mjs";
2
2
  import { Popover } from "./dynamic.mjs";
3
3
 
4
- export { Detail, Popover, PopoverStatic };
4
+ export { Detail, Popover, PopoverStatic, usePopoverIds };
@@ -1,9 +1,28 @@
1
1
  import { cn } from "../../utils/cn.mjs";
2
2
  import { Container } from "../Container/index.mjs";
3
+ import { createContext, useContext, useId } from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
 
5
6
  //#region src/components/Popover/static.tsx
6
7
  /**
8
+ * Name the trigger shares with its own `Detail` so both derive the same ids.
9
+ *
10
+ * The `identifier` prop names a *kind* of popover ("copy", "history"), and the
11
+ * same kind legitimately appears many times on one page — a documentation page
12
+ * renders one copy popover per code block. Deriving the ids from the identifier
13
+ * alone made every one of them share them, which is invalid HTML and leaves
14
+ * `aria-labelledby` pointing at whichever element the browser matched first.
15
+ */
16
+ const PopoverNameContext = createContext(null);
17
+ /** Ids of a popover pair, unique to the instance that rendered it. */
18
+ const usePopoverIds = (identifier) => {
19
+ const name = useContext(PopoverNameContext) ?? identifier;
20
+ return {
21
+ triggerId: `unrollable-panel-button-${name}`,
22
+ panelId: `unrollable-panel-${name}`
23
+ };
24
+ };
25
+ /**
7
26
  * Popover Component
8
27
  *
9
28
  * A versatile popover container that displays contextual content when triggered by hover
@@ -84,13 +103,20 @@ import { jsx } from "react/jsx-runtime";
84
103
  * @param props - Popover component props
85
104
  * @returns Trigger container with popover functionality
86
105
  */
87
- const PopoverStatic = ({ children, className, identifier, ...props }) => /* @__PURE__ */ jsx("div", {
88
- className: cn(`group/popover relative flex cursor-pointer`, className),
89
- id: `unrollable-panel-button-${identifier}`,
90
- "aria-haspopup": true,
91
- ...props,
92
- children
93
- });
106
+ const PopoverStatic = ({ children, className, identifier, ...props }) => {
107
+ const name = `${identifier}${useId()}`;
108
+ const { triggerId } = usePopoverIds(name);
109
+ return /* @__PURE__ */ jsx(PopoverNameContext, {
110
+ value: name,
111
+ children: /* @__PURE__ */ jsx("div", {
112
+ className: cn(`group/popover relative flex cursor-pointer`, className),
113
+ id: triggerId,
114
+ "aria-haspopup": true,
115
+ ...props,
116
+ children
117
+ })
118
+ });
119
+ };
94
120
  /**
95
121
  * Popover Detail Component
96
122
  *
@@ -154,18 +180,21 @@ const PopoverStatic = ({ children, className, identifier, ...props }) => /* @__P
154
180
  * @param props - Popover Detail component props
155
181
  * @returns Positioned popover content with animations and accessibility
156
182
  */
157
- const Detail = ({ children, isHidden = void 0, isOverable = true, isFocusable = false, xAlign = "start", yAlign = "below", identifier, className, displayArrow = true, ...props }) => /* @__PURE__ */ jsx(Container, {
158
- transparency: "xs",
159
- role: "group",
160
- "aria-hidden": isHidden,
161
- "aria-labelledby": `unrollable-panel-button-${identifier}`,
162
- id: `unrollable-panel-${identifier}`,
163
- className: cn("absolute z-60 min-w-full rounded-md ring-1 ring-neutral", xAlign === "start" && "left-0", xAlign === "center" && "left-1/2 -translate-x-1/2", xAlign === "end" && "right-0", yAlign === "below" && "top-[calc(100%+1rem)]", yAlign === "above" && "bottom-[calc(100%+1rem)]", displayArrow && "before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]", displayArrow && xAlign === "start" && "before:left-2", displayArrow && xAlign === "center" && "before:left-1/2 before:-translate-x-1/2", displayArrow && xAlign === "end" && "before:right-2", displayArrow && yAlign === "below" && "before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent", displayArrow && yAlign === "above" && "before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent", "overflow-x-visible opacity-0 transition-all duration-400 ease-in-out", isHidden !== false ? "invisible" : "visible opacity-100 delay-800", isOverable && `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`, isFocusable && `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`, className),
164
- ...props,
165
- children
166
- });
183
+ const Detail = ({ children, isHidden = void 0, isOverable = true, isFocusable = false, xAlign = "start", yAlign = "below", identifier, className, displayArrow = true, ...props }) => {
184
+ const { triggerId, panelId } = usePopoverIds(identifier);
185
+ return /* @__PURE__ */ jsx(Container, {
186
+ transparency: "xs",
187
+ role: "group",
188
+ "aria-hidden": isHidden,
189
+ "aria-labelledby": triggerId,
190
+ id: panelId,
191
+ className: cn("absolute z-60 min-w-full rounded-md ring-1 ring-neutral", xAlign === "start" && "left-0", xAlign === "center" && "left-1/2 -translate-x-1/2", xAlign === "end" && "right-0", yAlign === "below" && "top-[calc(100%+1rem)]", yAlign === "above" && "bottom-[calc(100%+1rem)]", displayArrow && "before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]", displayArrow && xAlign === "start" && "before:left-2", displayArrow && xAlign === "center" && "before:left-1/2 before:-translate-x-1/2", displayArrow && xAlign === "end" && "before:right-2", displayArrow && yAlign === "below" && "before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent", displayArrow && yAlign === "above" && "before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent", "overflow-x-visible opacity-0 transition-all duration-400 ease-in-out", isHidden !== false ? "invisible" : "visible opacity-100 delay-800", isOverable && `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`, isFocusable && `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`, className),
192
+ ...props,
193
+ children
194
+ });
195
+ };
167
196
  PopoverStatic.Detail = Detail;
168
197
 
169
198
  //#endregion
170
- export { Detail, PopoverStatic };
199
+ export { Detail, PopoverStatic, usePopoverIds };
171
200
  //# sourceMappingURL=static.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"static.mjs","names":[],"sources":["../../../../src/components/Popover/static.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport type {\n ComponentProps,\n DetailedHTMLProps,\n FC,\n HTMLAttributes,\n} from 'react';\nimport { Container } from '../Container';\n\n/**\n * Props for the main Popover component\n * Extends HTMLDivElement attributes for full DOM compatibility\n */\nexport type PopoverProps = DetailedHTMLProps<\n HTMLAttributes<HTMLDivElement>,\n HTMLDivElement\n> & {\n /** Unique identifier linking the trigger to its popover content for accessibility */\n identifier: string;\n};\n\n/**\n * Composite type for the Popover component with Detail subcomponent\n * Allows for Popover.Detail usage pattern\n */\nexport type PopoverType = FC<PopoverProps> & {\n Detail: FC<DetailProps>;\n};\n\n/**\n * Horizontal alignment options for popover positioning\n */\nexport type PopoverXAlign = 'start' | 'center' | 'end';\n\n/**\n * Vertical alignment options for popover positioning\n */\nexport type PopoverYAlign = 'below' | 'above';\n\n/**\n * Popover Component\n *\n * A versatile popover container that displays contextual content when triggered by hover\n * or focus interactions. Built with accessibility in mind and supports multiple positioning\n * options with smooth animations.\n *\n * Features:\n * - Hover and focus-based triggering\n * - Multiple positioning options (above/below, start/center/end)\n * - Accessibility compliant with ARIA attributes\n * - Smooth animations with configurable delays\n * - Optional directional arrows\n * - Automatic z-index management\n * - Responsive design support\n *\n * Architecture:\n * - Main Popover acts as trigger container\n * - Popover.Detail renders the actual popover content\n * - Uses CSS groups for coordinated hover/focus states\n * - Unique identifier system prevents conflicts\n *\n * @example\n * Basic hover popover:\n * ```jsx\n * <Popover identifier=\"help-tooltip\">\n * <button>Need Help?</button>\n *\n * <Popover.Detail identifier=\"help-tooltip\">\n * <div>This is helpful information!</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Focus-triggered popover:\n * ```jsx\n * <Popover identifier=\"focus-menu\">\n * <input placeholder=\"Focus me\" />\n *\n * <Popover.Detail\n * identifier=\"focus-menu\"\n * isFocusable\n * isOverable={false}\n * >\n * <div>Focus-only menu content</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Positioned popover with custom alignment:\n * ```jsx\n * <Popover identifier=\"positioned\">\n * <span>Hover me</span>\n *\n * <Popover.Detail\n * identifier=\"positioned\"\n * xAlign=\"end\"\n * yAlign=\"above\"\n * displayArrow={false}\n * >\n * <div>Above and right-aligned</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * Accessibility Features:\n * - Proper ARIA labeling and relationships\n * - Keyboard navigation support\n * - Screen reader compatibility\n * - Focus management\n *\n * Performance Considerations:\n * - CSS-only animations for smooth transitions\n * - Efficient group-based state management\n * - Minimal DOM updates during interactions\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nexport const PopoverStatic: PopoverType = ({\n children,\n className,\n identifier,\n ...props\n}) => (\n <div\n className={cn(`group/popover relative flex cursor-pointer`, className)}\n id={`unrollable-panel-button-${identifier}`}\n aria-haspopup\n {...props}\n >\n {children}\n </div>\n);\n\n/**\n * Props for the Popover.Detail component\n * Extends HTMLDivElement attributes for styling flexibility\n */\nexport type DetailProps = ComponentProps<typeof Container> & {\n /** Whether the popover responds to focus events on the trigger */\n isFocusable?: boolean;\n /** Controls visibility state - undefined allows automatic hover/focus control */\n isHidden?: boolean;\n /** Whether the popover responds to hover events on the trigger */\n isOverable?: boolean;\n /** Unique identifier matching the trigger's identifier for accessibility */\n identifier: string;\n /** Horizontal positioning relative to trigger */\n xAlign?: PopoverXAlign | `${PopoverXAlign}`;\n /** Vertical positioning relative to trigger */\n yAlign?: PopoverYAlign | `${PopoverYAlign}`;\n /** Whether to display the directional arrow indicator */\n displayArrow?: boolean;\n};\n\n/**\n * Popover Detail Component\n *\n * The actual popover content container with advanced positioning, animation, and\n * accessibility features. Automatically manages visibility based on trigger interactions.\n *\n * Features:\n * - Precise positioning with alignment options\n * - Smooth fade and slide animations\n * - Configurable directional arrows\n * - Hover and focus interaction support\n * - Accessibility-compliant ARIA attributes\n * - High z-index for overlay behavior\n * - Automatic visibility management\n *\n * Positioning System:\n * - X-axis: START (left-aligned), CENTER (centered), or END (right-aligned)\n * - Y-axis: BELOW (underneath) or ABOVE (on top)\n * - Automatic spacing with 1rem gap from trigger\n * - Responsive minimum width matching trigger\n *\n * Arrow Indicators:\n * - CSS-generated triangular arrows\n * - Positioned based on alignment settings\n * - Points toward trigger for visual connection\n * - Can be disabled for clean, minimal appearance\n *\n * Animation Behavior:\n * - Starts invisible with opacity: 0\n * - Smooth 400ms transitions with easing\n * - 800ms delay for hover states (prevents flicker)\n * - Immediate hiding when trigger loses focus/hover\n *\n * @example\n * Rich content popover:\n * ```jsx\n * <Popover.Detail identifier=\"rich-content\">\n * <div className=\"p-4\">\n * <h3>Popover Title</h3>\n * <p>Detailed information with multiple paragraphs.</p>\n * <button>Action Button</button>\n * </div>\n * </Popover.Detail>\n * ```\n *\n * @example\n * Menu-style popover:\n * ```jsx\n * <Popover.Detail\n * identifier=\"context-menu\"\n * displayArrow={false}\n * xAlign=\"end\"\n * >\n * <ul className=\"py-2\">\n * <li><button className=\"w-full px-4 py-2\">Edit</button></li>\n * <li><button className=\"w-full px-4 py-2\">Delete</button></li>\n * </ul>\n * </Popover.Detail>\n * ```\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n children,\n isHidden = undefined,\n isOverable = true,\n isFocusable = false,\n xAlign = 'start',\n yAlign = 'below',\n identifier,\n className,\n displayArrow = true,\n ...props\n}) => (\n <Container\n transparency=\"xs\"\n role=\"group\"\n aria-hidden={isHidden}\n aria-labelledby={`unrollable-panel-button-${identifier}`}\n id={`unrollable-panel-${identifier}`}\n className={cn(\n 'absolute z-60 min-w-full rounded-md ring-1 ring-neutral',\n\n /* Positioning */\n xAlign === 'start' && 'left-0',\n xAlign === 'center' && 'left-1/2 -translate-x-1/2',\n xAlign === 'end' && 'right-0',\n yAlign === 'below' && 'top-[calc(100%+1rem)]',\n yAlign === 'above' && 'bottom-[calc(100%+1rem)]',\n\n /* Arrow indicator */\n displayArrow &&\n 'before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]',\n\n /* Horizontal positioning */\n displayArrow && xAlign === 'start' && 'before:left-2',\n displayArrow &&\n xAlign === 'center' &&\n 'before:left-1/2 before:-translate-x-1/2',\n displayArrow && xAlign === 'end' && 'before:right-2',\n\n /* Arrow pointing up (when popover is below trigger) */\n displayArrow &&\n yAlign === 'below' &&\n 'before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent',\n\n /* Arrow pointing down (when popover is above trigger) */\n displayArrow &&\n yAlign === 'above' &&\n 'before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent',\n\n /* Visibility management */\n 'overflow-x-visible opacity-0 transition-all duration-400 ease-in-out',\n isHidden !== false ? 'invisible' : 'visible opacity-100 delay-800',\n isOverable &&\n `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`,\n isFocusable &&\n `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`,\n className\n )}\n {...props}\n >\n {children}\n </Container>\n);\n\nPopoverStatic.Detail = Detail;\n\n// Export Detail for use in dynamic version\nexport { Detail };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHA,MAAa,iBAA8B,EACzC,UACA,WACA,YACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,8CAA8C,SAAS;CACrE,IAAI,2BAA2B;CAC/B;CACA,GAAI;CAEH;AACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFP,MAAM,UAA2B,EAC/B,UACA,WAAW,QACX,aAAa,MACb,cAAc,OACd,SAAS,SACT,SAAS,SACT,YACA,WACA,eAAe,MACf,GAAG,YAEH,oBAAC,WAAD;CACE,cAAa;CACb,MAAK;CACL,eAAa;CACb,mBAAiB,2BAA2B;CAC5C,IAAI,oBAAoB;CACxB,WAAW,GACT,2DAGA,WAAW,WAAW,UACtB,WAAW,YAAY,6BACvB,WAAW,SAAS,WACpB,WAAW,WAAW,yBACtB,WAAW,WAAW,4BAGtB,gBACE,8EAGF,gBAAgB,WAAW,WAAW,iBACtC,gBACE,WAAW,YACX,2CACF,gBAAgB,WAAW,SAAS,kBAGpC,gBACE,WAAW,WACX,wKAGF,gBACE,WAAW,WACX,2KAGF,wEACA,aAAa,QAAQ,cAAc,iCACnC,cACE,6FACF,eACE,kHACF,SACF;CACA,GAAI;CAEH;AACQ;AAGb,cAAc,SAAS"}
1
+ {"version":3,"file":"static.mjs","names":[],"sources":["../../../../src/components/Popover/static.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport {\n type ComponentProps,\n createContext,\n type DetailedHTMLProps,\n type FC,\n type HTMLAttributes,\n useContext,\n useId,\n} from 'react';\nimport { Container } from '../Container';\n\n/**\n * Name the trigger shares with its own `Detail` so both derive the same ids.\n *\n * The `identifier` prop names a *kind* of popover (\"copy\", \"history\"), and the\n * same kind legitimately appears many times on one page — a documentation page\n * renders one copy popover per code block. Deriving the ids from the identifier\n * alone made every one of them share them, which is invalid HTML and leaves\n * `aria-labelledby` pointing at whichever element the browser matched first.\n */\nconst PopoverNameContext = createContext<string | null>(null);\n\n/** Ids of a popover pair, unique to the instance that rendered it. */\nexport const usePopoverIds = (identifier: string) => {\n const name = useContext(PopoverNameContext) ?? identifier;\n\n return {\n triggerId: `unrollable-panel-button-${name}`,\n panelId: `unrollable-panel-${name}`,\n };\n};\n\n/**\n * Props for the main Popover component\n * Extends HTMLDivElement attributes for full DOM compatibility\n */\nexport type PopoverProps = DetailedHTMLProps<\n HTMLAttributes<HTMLDivElement>,\n HTMLDivElement\n> & {\n /** Unique identifier linking the trigger to its popover content for accessibility */\n identifier: string;\n};\n\n/**\n * Composite type for the Popover component with Detail subcomponent\n * Allows for Popover.Detail usage pattern\n */\nexport type PopoverType = FC<PopoverProps> & {\n Detail: FC<DetailProps>;\n};\n\n/**\n * Horizontal alignment options for popover positioning\n */\nexport type PopoverXAlign = 'start' | 'center' | 'end';\n\n/**\n * Vertical alignment options for popover positioning\n */\nexport type PopoverYAlign = 'below' | 'above';\n\n/**\n * Popover Component\n *\n * A versatile popover container that displays contextual content when triggered by hover\n * or focus interactions. Built with accessibility in mind and supports multiple positioning\n * options with smooth animations.\n *\n * Features:\n * - Hover and focus-based triggering\n * - Multiple positioning options (above/below, start/center/end)\n * - Accessibility compliant with ARIA attributes\n * - Smooth animations with configurable delays\n * - Optional directional arrows\n * - Automatic z-index management\n * - Responsive design support\n *\n * Architecture:\n * - Main Popover acts as trigger container\n * - Popover.Detail renders the actual popover content\n * - Uses CSS groups for coordinated hover/focus states\n * - Unique identifier system prevents conflicts\n *\n * @example\n * Basic hover popover:\n * ```jsx\n * <Popover identifier=\"help-tooltip\">\n * <button>Need Help?</button>\n *\n * <Popover.Detail identifier=\"help-tooltip\">\n * <div>This is helpful information!</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Focus-triggered popover:\n * ```jsx\n * <Popover identifier=\"focus-menu\">\n * <input placeholder=\"Focus me\" />\n *\n * <Popover.Detail\n * identifier=\"focus-menu\"\n * isFocusable\n * isOverable={false}\n * >\n * <div>Focus-only menu content</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * @example\n * Positioned popover with custom alignment:\n * ```jsx\n * <Popover identifier=\"positioned\">\n * <span>Hover me</span>\n *\n * <Popover.Detail\n * identifier=\"positioned\"\n * xAlign=\"end\"\n * yAlign=\"above\"\n * displayArrow={false}\n * >\n * <div>Above and right-aligned</div>\n * </Popover.Detail>\n * </Popover>\n * ```\n *\n * Accessibility Features:\n * - Proper ARIA labeling and relationships\n * - Keyboard navigation support\n * - Screen reader compatibility\n * - Focus management\n *\n * Performance Considerations:\n * - CSS-only animations for smooth transitions\n * - Efficient group-based state management\n * - Minimal DOM updates during interactions\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nexport const PopoverStatic: PopoverType = ({\n children,\n className,\n identifier,\n ...props\n}) => {\n const name = `${identifier}${useId()}`;\n const { triggerId } = usePopoverIds(name);\n\n return (\n <PopoverNameContext value={name}>\n <div\n className={cn(`group/popover relative flex cursor-pointer`, className)}\n id={triggerId}\n aria-haspopup\n {...props}\n >\n {children}\n </div>\n </PopoverNameContext>\n );\n};\n\n/**\n * Props for the Popover.Detail component\n * Extends HTMLDivElement attributes for styling flexibility\n */\nexport type DetailProps = ComponentProps<typeof Container> & {\n /** Whether the popover responds to focus events on the trigger */\n isFocusable?: boolean;\n /** Controls visibility state - undefined allows automatic hover/focus control */\n isHidden?: boolean;\n /** Whether the popover responds to hover events on the trigger */\n isOverable?: boolean;\n /** Unique identifier matching the trigger's identifier for accessibility */\n identifier: string;\n /** Horizontal positioning relative to trigger */\n xAlign?: PopoverXAlign | `${PopoverXAlign}`;\n /** Vertical positioning relative to trigger */\n yAlign?: PopoverYAlign | `${PopoverYAlign}`;\n /** Whether to display the directional arrow indicator */\n displayArrow?: boolean;\n};\n\n/**\n * Popover Detail Component\n *\n * The actual popover content container with advanced positioning, animation, and\n * accessibility features. Automatically manages visibility based on trigger interactions.\n *\n * Features:\n * - Precise positioning with alignment options\n * - Smooth fade and slide animations\n * - Configurable directional arrows\n * - Hover and focus interaction support\n * - Accessibility-compliant ARIA attributes\n * - High z-index for overlay behavior\n * - Automatic visibility management\n *\n * Positioning System:\n * - X-axis: START (left-aligned), CENTER (centered), or END (right-aligned)\n * - Y-axis: BELOW (underneath) or ABOVE (on top)\n * - Automatic spacing with 1rem gap from trigger\n * - Responsive minimum width matching trigger\n *\n * Arrow Indicators:\n * - CSS-generated triangular arrows\n * - Positioned based on alignment settings\n * - Points toward trigger for visual connection\n * - Can be disabled for clean, minimal appearance\n *\n * Animation Behavior:\n * - Starts invisible with opacity: 0\n * - Smooth 400ms transitions with easing\n * - 800ms delay for hover states (prevents flicker)\n * - Immediate hiding when trigger loses focus/hover\n *\n * @example\n * Rich content popover:\n * ```jsx\n * <Popover.Detail identifier=\"rich-content\">\n * <div className=\"p-4\">\n * <h3>Popover Title</h3>\n * <p>Detailed information with multiple paragraphs.</p>\n * <button>Action Button</button>\n * </div>\n * </Popover.Detail>\n * ```\n *\n * @example\n * Menu-style popover:\n * ```jsx\n * <Popover.Detail\n * identifier=\"context-menu\"\n * displayArrow={false}\n * xAlign=\"end\"\n * >\n * <ul className=\"py-2\">\n * <li><button className=\"w-full px-4 py-2\">Edit</button></li>\n * <li><button className=\"w-full px-4 py-2\">Delete</button></li>\n * </ul>\n * </Popover.Detail>\n * ```\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n children,\n isHidden = undefined,\n isOverable = true,\n isFocusable = false,\n xAlign = 'start',\n yAlign = 'below',\n identifier,\n className,\n displayArrow = true,\n ...props\n}) => {\n const { triggerId, panelId } = usePopoverIds(identifier);\n\n return (\n <Container\n transparency=\"xs\"\n role=\"group\"\n aria-hidden={isHidden}\n aria-labelledby={triggerId}\n id={panelId}\n className={cn(\n 'absolute z-60 min-w-full rounded-md ring-1 ring-neutral',\n\n /* Positioning */\n xAlign === 'start' && 'left-0',\n xAlign === 'center' && 'left-1/2 -translate-x-1/2',\n xAlign === 'end' && 'right-0',\n yAlign === 'below' && 'top-[calc(100%+1rem)]',\n yAlign === 'above' && 'bottom-[calc(100%+1rem)]',\n\n /* Arrow indicator */\n displayArrow &&\n 'before:absolute before:z-[999] before:h-0 before:w-0 before:content-[\"\"]',\n\n /* Horizontal positioning */\n displayArrow && xAlign === 'start' && 'before:left-2',\n displayArrow &&\n xAlign === 'center' &&\n 'before:left-1/2 before:-translate-x-1/2',\n displayArrow && xAlign === 'end' && 'before:right-2',\n\n /* Arrow pointing up (when popover is below trigger) */\n displayArrow &&\n yAlign === 'below' &&\n 'before:-top-2.5 before:border-r-[10px] before:border-r-transparent before:border-b-[10px] before:border-b-neutral before:border-l-[10px] before:border-l-transparent',\n\n /* Arrow pointing down (when popover is above trigger) */\n displayArrow &&\n yAlign === 'above' &&\n 'before:-bottom-2.5 before:border-t-[10px] before:border-t-neutral before:border-r-[10px] before:border-r-transparent before:border-l-[10px] before:border-l-transparent',\n\n /* Visibility management */\n 'overflow-x-visible opacity-0 transition-all duration-400 ease-in-out',\n isHidden !== false ? 'invisible' : 'visible opacity-100 delay-800',\n isOverable &&\n `group-hover/popover:visible group-hover/popover:opacity-100 group-hover/popover:delay-800`,\n isFocusable &&\n `group-focus-within/popover:visible group-focus-within/popover:opacity-100 group-focus-within/popover:delay-800`,\n className\n )}\n {...props}\n >\n {children}\n </Container>\n );\n};\n\nPopoverStatic.Detail = Detail;\n\n// Export Detail for use in dynamic version\nexport { Detail };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,cAA6B,IAAI;;AAG5D,MAAa,iBAAiB,eAAuB;CACnD,MAAM,OAAO,WAAW,kBAAkB,KAAK;CAE/C,OAAO;EACL,WAAW,2BAA2B;EACtC,SAAS,oBAAoB;CAC/B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiHA,MAAa,iBAA8B,EACzC,UACA,WACA,YACA,GAAG,YACC;CACJ,MAAM,OAAO,GAAG,aAAa,MAAM;CACnC,MAAM,EAAE,cAAc,cAAc,IAAI;CAExC,OACE,oBAAC,oBAAD;EAAoB,OAAO;YACzB,oBAAC,OAAD;GACE,WAAW,GAAG,8CAA8C,SAAS;GACrE,IAAI;GACJ;GACA,GAAI;GAEH;EACE;CACa;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,MAAM,UAA2B,EAC/B,UACA,WAAW,QACX,aAAa,MACb,cAAc,OACd,SAAS,SACT,SAAS,SACT,YACA,WACA,eAAe,MACf,GAAG,YACC;CACJ,MAAM,EAAE,WAAW,YAAY,cAAc,UAAU;CAEvD,OACE,oBAAC,WAAD;EACE,cAAa;EACb,MAAK;EACL,eAAa;EACb,mBAAiB;EACjB,IAAI;EACJ,WAAW,GACT,2DAGA,WAAW,WAAW,UACtB,WAAW,YAAY,6BACvB,WAAW,SAAS,WACpB,WAAW,WAAW,yBACtB,WAAW,WAAW,4BAGtB,gBACE,8EAGF,gBAAgB,WAAW,WAAW,iBACtC,gBACE,WAAW,YACX,2CACF,gBAAgB,WAAW,SAAS,kBAGpC,gBACE,WAAW,WACX,wKAGF,gBACE,WAAW,WACX,2KAGF,wEACA,aAAa,QAAQ,cAAc,iCACnC,cACE,6FACF,eACE,kHACF,SACF;EACA,GAAI;EAEH;CACQ;AAEf;AAEA,cAAc,SAAS"}