panelui-native 0.82.0 → 0.84.0

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 (54) hide show
  1. package/lib/module/components/bottom-sheet/index.js +111 -16
  2. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  3. package/lib/module/components/circular-text/circular-text-geometry.js +63 -0
  4. package/lib/module/components/circular-text/circular-text-geometry.js.map +1 -0
  5. package/lib/module/components/circular-text/index.js +146 -0
  6. package/lib/module/components/circular-text/index.js.map +1 -0
  7. package/lib/module/components/image-generation/dot-field.js +66 -9
  8. package/lib/module/components/image-generation/dot-field.js.map +1 -1
  9. package/lib/module/components/image-generation/index.js +65 -23
  10. package/lib/module/components/image-generation/index.js.map +1 -1
  11. package/lib/module/components/marquee/index.js +12 -5
  12. package/lib/module/components/marquee/index.js.map +1 -1
  13. package/lib/module/components/panelside/index.js +15 -417
  14. package/lib/module/components/panelside/index.js.map +1 -1
  15. package/lib/module/components/popover/index.js +119 -7
  16. package/lib/module/components/popover/index.js.map +1 -1
  17. package/lib/module/icons/index.js +24 -4
  18. package/lib/module/icons/index.js.map +1 -1
  19. package/lib/module/index.js +1 -0
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/module/native/index.js +52 -0
  22. package/lib/module/native/index.js.map +1 -1
  23. package/lib/typescript/src/components/bottom-sheet/index.d.ts +17 -1
  24. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  25. package/lib/typescript/src/components/circular-text/circular-text-geometry.d.ts +39 -0
  26. package/lib/typescript/src/components/circular-text/circular-text-geometry.d.ts.map +1 -0
  27. package/lib/typescript/src/components/circular-text/index.d.ts +69 -0
  28. package/lib/typescript/src/components/circular-text/index.d.ts.map +1 -0
  29. package/lib/typescript/src/components/image-generation/dot-field.d.ts +36 -1
  30. package/lib/typescript/src/components/image-generation/dot-field.d.ts.map +1 -1
  31. package/lib/typescript/src/components/image-generation/index.d.ts +16 -8
  32. package/lib/typescript/src/components/image-generation/index.d.ts.map +1 -1
  33. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  34. package/lib/typescript/src/components/panelside/index.d.ts +10 -202
  35. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  36. package/lib/typescript/src/components/popover/index.d.ts +25 -1
  37. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  38. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  39. package/lib/typescript/src/index.d.ts +2 -1
  40. package/lib/typescript/src/index.d.ts.map +1 -1
  41. package/lib/typescript/src/native/index.d.ts +66 -0
  42. package/lib/typescript/src/native/index.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/components/bottom-sheet/index.tsx +139 -16
  45. package/src/components/circular-text/circular-text-geometry.ts +80 -0
  46. package/src/components/circular-text/index.tsx +217 -0
  47. package/src/components/image-generation/dot-field.ts +65 -9
  48. package/src/components/image-generation/index.tsx +67 -18
  49. package/src/components/marquee/index.tsx +12 -5
  50. package/src/components/panelside/index.tsx +16 -604
  51. package/src/components/popover/index.tsx +146 -4
  52. package/src/icons/index.tsx +23 -2
  53. package/src/index.ts +1 -6
  54. package/src/native/index.ts +100 -0
@@ -15,14 +15,22 @@
15
15
  *
16
16
  * ## The dot field
17
17
  *
18
- * A grid of dots with a soft band of light travelling across it. Each column
19
- * is one animated view holding its own dots, so a field of five hundred dots
20
- * costs twenty-two worklets a frame rather than five hundred — the dots inside
21
- * a column never move independently of it, and at this scale nobody can tell.
18
+ * A grid of dots with a soft light moving through it, drawn as three layers:
19
+ * the field at rest, and two copies of the lit dots fading into one another.
22
20
  *
23
- * The per-dot brightness underneath the band is fixed, and comes from the
24
- * dot's own coordinates. Without it the field reads as clean vertical stripes
25
- * sweeping past; with it, it reads as the individual dots lighting up.
21
+ * Every picture the loop will ever draw is built when the box is measured, so
22
+ * running it hands over a string that already exists. That leaves twenty-four
23
+ * pictures to spread across the pass, which on its own is a flipbook — a new
24
+ * one every 175ms, however fast the screen refreshes. The two layers are what
25
+ * fills the gap: one holds the frame being left and one the frame being
26
+ * arrived at, and only their opacities move, which the compositor animates at
27
+ * the display's own rate without the drawing being touched.
28
+ *
29
+ * They are opacities on views rather than on the dots, and that is not a
30
+ * detail. A value that changes every frame sitting beside the path string
31
+ * pushes the path every frame too, and every push of a path is a re-parse of
32
+ * several hundred subpaths — which is the cost this whole arrangement exists
33
+ * to avoid. Kept apart, the path is pushed only when it actually changes.
26
34
  *
27
35
  * ## What arrives, and when
28
36
  *
@@ -34,11 +42,11 @@
34
42
  *
35
43
  * ## Reduced motion
36
44
  *
37
- * The band stops, and the field is drawn at one still frame of itself. A
45
+ * The light stops, and the field holds wherever the clock was left. A
38
46
  * placeholder that shows nothing is indistinguishable from a component that
39
47
  * failed to load, so this is a quieter picture rather than an empty one.
40
48
  */
41
- import { useEffect, useId, useMemo, useState, type ReactNode } from 'react';
49
+ import { useEffect, useMemo, useState, type ReactNode } from 'react';
42
50
  import {
43
51
  Pressable,
44
52
  StyleSheet,
@@ -61,13 +69,14 @@ import Animated, {
61
69
  } from 'react-native-reanimated';
62
70
  import { tv } from 'tailwind-variants';
63
71
  import { useCSSVariable } from 'uniwind';
64
- import { useDirectionSign } from '../../hooks/use-direction';
65
72
  import { AlertTriangleIcon, CheckIcon, RotateCcwIcon } from '../../icons';
66
73
  import { Text } from '../../primitives/text';
67
74
  import { cn } from '../../utils/cn';
68
75
  import {
69
76
  DOT_REST_OPACITY,
70
- frameAt,
77
+ FRAMES,
78
+ crossfadeAlphas,
79
+ framePhase,
71
80
  gridPath,
72
81
  litFrames,
73
82
  type DotFieldAnimation,
@@ -241,8 +250,21 @@ function ImageGenerationField({
241
250
  * light is somewhere legible rather than dead centre or absent, which is the
242
251
  * difference between "not animating" and "failed to load".
243
252
  */
244
- const animatedProps = useAnimatedProps(() => ({
245
- d: frames[frameAt(clock.value, animation)] ?? '',
253
+ const slotA = useAnimatedProps(() => ({
254
+ d: frames[Math.floor(framePhase(clock.value, animation))] ?? '',
255
+ }));
256
+ const slotB = useAnimatedProps(() => ({
257
+ d: frames[(Math.floor(framePhase(clock.value, animation)) + 1) % FRAMES] ?? '',
258
+ }));
259
+
260
+ // Only these move between one frame and the next, and a view's opacity is a
261
+ // layer alpha — so the drawing underneath is composited again rather than
262
+ // drawn again.
263
+ const fadeOut = useAnimatedStyle(() => ({
264
+ opacity: crossfadeAlphas(framePhase(clock.value, animation) % 1)[0],
265
+ }));
266
+ const fadeIn = useAnimatedStyle(() => ({
267
+ opacity: crossfadeAlphas(framePhase(clock.value, animation) % 1)[1],
246
268
  }));
247
269
 
248
270
  const onLayout = (event: LayoutChangeEvent) => {
@@ -261,14 +283,41 @@ function ImageGenerationField({
261
283
  {...props}
262
284
  >
263
285
  {grid ? (
264
- <Svg width={size.width} height={size.height}>
286
+ <>
265
287
  {/* The field at rest, under everything and never touched again. */}
266
- <Path d={grid} fill={color} fillOpacity={DOT_REST_OPACITY} />
288
+ <Svg
289
+ width={size.width}
290
+ height={size.height}
291
+ style={StyleSheet.absoluteFill}
292
+ >
293
+ <Path d={grid} fill={color} fillOpacity={DOT_REST_OPACITY} />
294
+ </Svg>
267
295
  {/* The dots the light has reached, drawn larger over their own
268
296
  resting copies rather than instead of them — so a dot brightens
269
- in place instead of appearing to move. */}
270
- <AnimatedPath animatedProps={animatedProps} fill={color} fillOpacity={0.9} />
271
- </Svg>
297
+ in place instead of appearing to move.
298
+
299
+ Two of them: the frame being left and the frame being arrived at.
300
+ Each carries its dots at full strength and the pair is brought to
301
+ LIT_OPACITY by the views, because what fades has to be the layer
302
+ and not the fill — see crossfadeAlphas for why the ramps are not
303
+ simply t and 1 - t. */}
304
+ <Animated.View
305
+ style={[StyleSheet.absoluteFill, fadeOut]}
306
+ pointerEvents="none"
307
+ >
308
+ <Svg width={size.width} height={size.height}>
309
+ <AnimatedPath animatedProps={slotA} fill={color} />
310
+ </Svg>
311
+ </Animated.View>
312
+ <Animated.View
313
+ style={[StyleSheet.absoluteFill, fadeIn]}
314
+ pointerEvents="none"
315
+ >
316
+ <Svg width={size.width} height={size.height}>
317
+ <AnimatedPath animatedProps={slotB} fill={color} />
318
+ </Svg>
319
+ </Animated.View>
320
+ </>
272
321
  ) : null}
273
322
  </View>
274
323
  );
@@ -258,12 +258,19 @@ function MarqueeRoot({
258
258
  };
259
259
 
260
260
  /*
261
- * Whether there is motion here at all: content that measured, a speed to
262
- * travel it at, and a `playing` that has not already stopped it from
263
- * outside. A control offering to pause a still marquee is the same lie as a
264
- * disabled button with no reason on it.
261
+ * Whether there is motion here at all: copies on screen, a speed to travel
262
+ * them at, and a `playing` that has not already stopped it from outside. A
263
+ * control offering to pause a still marquee is the same lie as a disabled
264
+ * button with no reason on it.
265
+ *
266
+ * The copy count, not the period. Content that measured while its container
267
+ * did not has a period — it is the content's own length — and no copies to
268
+ * draw, because there is no width to lay them across. Asking the period
269
+ * meant a row that renders nothing still reported itself live, and a group
270
+ * drew a pause control for it: a marquee that is one button and no content,
271
+ * which is the shape of the bug this replaced.
265
272
  */
266
- const live = playing && !reducedMotion && period > 0 && speed > 0;
273
+ const live = playing && !reducedMotion && layout.count > 0 && speed > 0;
267
274
 
268
275
  // Reported to the group, if there is one, so its single control can be
269
276
  // decided the same way this one's is.