@ansh-ui/react 0.2.0 → 0.3.1

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 (92) hide show
  1. package/dist/{chunk-C5JG6TMM.cjs → chunk-2U7BHYIQ.cjs} +143 -143
  2. package/dist/chunk-2U7BHYIQ.cjs.map +1 -0
  3. package/dist/{chunk-HO2YSF5O.js → chunk-742E5FQS.js} +3 -3
  4. package/dist/chunk-742E5FQS.js.map +1 -0
  5. package/dist/{chunk-QO3E7TJS.js → chunk-7QGB2X7R.js} +36 -28
  6. package/dist/chunk-7QGB2X7R.js.map +1 -0
  7. package/dist/{chunk-EGG4CP7C.js → chunk-BQJ6H3HC.js} +3 -2
  8. package/dist/chunk-BQJ6H3HC.js.map +1 -0
  9. package/dist/{chunk-OLTS6G5M.js → chunk-GNA33V2J.js} +11 -6
  10. package/dist/chunk-GNA33V2J.js.map +1 -0
  11. package/dist/{chunk-C32LKF23.cjs → chunk-KTLTYHAT.cjs} +3 -3
  12. package/dist/chunk-KTLTYHAT.cjs.map +1 -0
  13. package/dist/{chunk-AVPEA5PB.cjs → chunk-MPX7QT4R.cjs} +4 -4
  14. package/dist/chunk-MPX7QT4R.cjs.map +1 -0
  15. package/dist/{chunk-T4AK2KLM.js → chunk-NJUVVMJV.js} +3 -3
  16. package/dist/chunk-NJUVVMJV.js.map +1 -0
  17. package/dist/{chunk-IBXLC5IE.js → chunk-P5MRISBP.js} +19 -3
  18. package/dist/chunk-P5MRISBP.js.map +1 -0
  19. package/dist/{chunk-WGYNCSTP.cjs → chunk-PUW6BTPQ.cjs} +3 -2
  20. package/dist/chunk-PUW6BTPQ.cjs.map +1 -0
  21. package/dist/{chunk-LDCZQP3R.js → chunk-RGGNLAAA.js} +122 -122
  22. package/dist/chunk-RGGNLAAA.js.map +1 -0
  23. package/dist/{chunk-6VRF7X5C.cjs → chunk-TEPQGFR4.cjs} +19 -2
  24. package/dist/chunk-TEPQGFR4.cjs.map +1 -0
  25. package/dist/{chunk-OJN5JBNP.cjs → chunk-VJI6V27F.cjs} +99 -99
  26. package/dist/chunk-VJI6V27F.cjs.map +1 -0
  27. package/dist/{chunk-HPGCILO4.js → chunk-XRKZPLUT.js} +99 -99
  28. package/dist/chunk-XRKZPLUT.js.map +1 -0
  29. package/dist/{chunk-DATBU46V.cjs → chunk-XYT6OXKP.cjs} +10 -4
  30. package/dist/chunk-XYT6OXKP.cjs.map +1 -0
  31. package/dist/{chunk-RHTLNSVW.cjs → chunk-YVJB7AAZ.cjs} +39 -31
  32. package/dist/chunk-YVJB7AAZ.cjs.map +1 -0
  33. package/dist/data.cjs +16 -16
  34. package/dist/data.d.cts +25 -23
  35. package/dist/data.d.ts +25 -23
  36. package/dist/data.js +1 -1
  37. package/dist/form.cjs +37 -37
  38. package/dist/form.d.cts +4 -1
  39. package/dist/form.d.ts +4 -1
  40. package/dist/form.js +2 -2
  41. package/dist/index.cjs +666 -135
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +220 -7
  44. package/dist/index.d.ts +220 -7
  45. package/dist/index.js +530 -14
  46. package/dist/index.js.map +1 -1
  47. package/dist/layout.cjs +13 -13
  48. package/dist/layout.d.cts +11 -2
  49. package/dist/layout.d.ts +11 -2
  50. package/dist/layout.js +1 -1
  51. package/dist/{motion-CQmCq6_k.d.ts → motion-C6WfBFES.d.cts} +23 -19
  52. package/dist/{motion-CSX4g2N8.d.cts → motion-ugXmf505.d.ts} +23 -19
  53. package/dist/motion.cjs +19 -19
  54. package/dist/motion.d.cts +2 -2
  55. package/dist/motion.d.ts +2 -2
  56. package/dist/motion.js +2 -2
  57. package/dist/navigation.cjs +8 -8
  58. package/dist/navigation.d.cts +3 -2
  59. package/dist/navigation.d.ts +3 -2
  60. package/dist/navigation.js +1 -1
  61. package/dist/overlay.cjs +16 -16
  62. package/dist/overlay.d.cts +17 -14
  63. package/dist/overlay.d.ts +17 -14
  64. package/dist/overlay.js +2 -2
  65. package/dist/{position-DVScKcaa.d.cts → position-CW6cGpSF.d.cts} +2 -1
  66. package/dist/{position-DVScKcaa.d.ts → position-CW6cGpSF.d.ts} +2 -1
  67. package/dist/primitives.cjs +25 -21
  68. package/dist/primitives.d.cts +5 -4
  69. package/dist/primitives.d.ts +5 -4
  70. package/dist/primitives.js +2 -2
  71. package/dist/{sidebar-D-PafAXw.d.cts → sidebar-Dyt67t2z.d.cts} +2 -1
  72. package/dist/{sidebar-D-PafAXw.d.ts → sidebar-Dyt67t2z.d.ts} +2 -1
  73. package/dist/styles.css +464 -0
  74. package/dist/{use-observers-B9qtimrk.d.cts → use-observers-DvA7hSz4.d.cts} +10 -1
  75. package/dist/{use-observers-B9qtimrk.d.ts → use-observers-DvA7hSz4.d.ts} +10 -1
  76. package/package.json +3 -3
  77. package/dist/chunk-6VRF7X5C.cjs.map +0 -1
  78. package/dist/chunk-AVPEA5PB.cjs.map +0 -1
  79. package/dist/chunk-C32LKF23.cjs.map +0 -1
  80. package/dist/chunk-C5JG6TMM.cjs.map +0 -1
  81. package/dist/chunk-DATBU46V.cjs.map +0 -1
  82. package/dist/chunk-EGG4CP7C.js.map +0 -1
  83. package/dist/chunk-HO2YSF5O.js.map +0 -1
  84. package/dist/chunk-HPGCILO4.js.map +0 -1
  85. package/dist/chunk-IBXLC5IE.js.map +0 -1
  86. package/dist/chunk-LDCZQP3R.js.map +0 -1
  87. package/dist/chunk-OJN5JBNP.cjs.map +0 -1
  88. package/dist/chunk-OLTS6G5M.js.map +0 -1
  89. package/dist/chunk-QO3E7TJS.js.map +0 -1
  90. package/dist/chunk-RHTLNSVW.cjs.map +0 -1
  91. package/dist/chunk-T4AK2KLM.js.map +0 -1
  92. package/dist/chunk-WGYNCSTP.cjs.map +0 -1
package/dist/styles.css CHANGED
@@ -1294,6 +1294,353 @@
1294
1294
  }
1295
1295
  }
1296
1296
 
1297
+ /* ==========================================================================
1298
+ Motion UI — presentation components with motion of their own.
1299
+
1300
+ Split out of `components.css`, which is already ~830 lines. Everything here targets a plain
1301
+ `ansh-*` class or a `data-*` attribute and contains nothing React-specific, so it is shared
1302
+ verbatim with `@ansh-ui/vue`'s build the same way every other stylesheet in this directory is.
1303
+ ========================================================================== */
1304
+
1305
+ /* --- Rotating headline word ---------------------------------------------- */
1306
+
1307
+ /*
1308
+ * Deliberately NOT `overflow: hidden` on this element. An inline-level box with clipped overflow
1309
+ * is baseline-aligned by its bottom margin edge rather than by its text, which drops the rotating
1310
+ * word below the line of the sentence containing it. The clipping happens on `__clip` instead,
1311
+ * which is absolutely positioned and so takes no part in baseline alignment.
1312
+ */
1313
+ .ansh-rotate {
1314
+ position: relative;
1315
+ display: inline-block;
1316
+ }
1317
+
1318
+ /*
1319
+ * The width and baseline holder, in normal flow. `visibility: hidden` rather than `display: none`
1320
+ * — a display-none element contributes no size, which would defeat the point of measuring the
1321
+ * longest word.
1322
+ */
1323
+ .ansh-rotate__sizer {
1324
+ display: grid;
1325
+ grid-template-areas: 'word';
1326
+ visibility: hidden;
1327
+ pointer-events: none;
1328
+ }
1329
+
1330
+ .ansh-rotate__ghost {
1331
+ grid-area: word;
1332
+ white-space: nowrap;
1333
+ }
1334
+
1335
+ .ansh-rotate__clip {
1336
+ position: absolute;
1337
+ inset: 0;
1338
+ overflow: hidden;
1339
+ }
1340
+
1341
+ .ansh-rotate__word {
1342
+ display: inline-block;
1343
+ white-space: nowrap;
1344
+ will-change: transform, opacity;
1345
+ }
1346
+
1347
+ .ansh-rotate[data-animation='slide'] .ansh-rotate__word {
1348
+ animation: ansh-rotate-slide var(--ansh-duration-slow, 420ms) var(--ansh-ease-emphasized) both;
1349
+ }
1350
+
1351
+ .ansh-rotate[data-animation='fade'] .ansh-rotate__word {
1352
+ animation: ansh-rotate-fade var(--ansh-duration-slow, 420ms) var(--ansh-ease-standard) both;
1353
+ }
1354
+
1355
+ .ansh-rotate[data-animation='flip'] .ansh-rotate__word {
1356
+ animation: ansh-rotate-flip var(--ansh-duration-slow, 420ms) var(--ansh-ease-emphasized) both;
1357
+ transform-origin: 50% 0;
1358
+ }
1359
+
1360
+ @keyframes ansh-rotate-slide {
1361
+ from {
1362
+ transform: translateY(100%);
1363
+ opacity: 0;
1364
+ }
1365
+ to {
1366
+ transform: translateY(0);
1367
+ opacity: 1;
1368
+ }
1369
+ }
1370
+
1371
+ @keyframes ansh-rotate-fade {
1372
+ from {
1373
+ opacity: 0;
1374
+ }
1375
+ to {
1376
+ opacity: 1;
1377
+ }
1378
+ }
1379
+
1380
+ @keyframes ansh-rotate-flip {
1381
+ from {
1382
+ transform: rotateX(-90deg);
1383
+ opacity: 0;
1384
+ }
1385
+ to {
1386
+ transform: rotateX(0);
1387
+ opacity: 1;
1388
+ }
1389
+ }
1390
+
1391
+ /* --- Before/after comparison --------------------------------------------- */
1392
+
1393
+ .ansh-compare {
1394
+ position: relative;
1395
+ overflow: hidden;
1396
+ border-radius: var(--ansh-radius-lg);
1397
+ /* Without this a drag that starts on the surface scrolls the page on touch instead of moving
1398
+ the divider — the single most common defect in comparison sliders on mobile. */
1399
+ touch-action: none;
1400
+ user-select: none;
1401
+ }
1402
+
1403
+ .ansh-compare__layer {
1404
+ position: absolute;
1405
+ inset: 0;
1406
+ }
1407
+
1408
+ .ansh-compare__layer > * {
1409
+ width: 100%;
1410
+ height: 100%;
1411
+ }
1412
+
1413
+ /*
1414
+ * The revealed layer sits above and is clipped. Clipping rather than resizing is what keeps its
1415
+ * contents from reflowing as the handle moves.
1416
+ */
1417
+ .ansh-compare__layer--before {
1418
+ z-index: 1;
1419
+ }
1420
+
1421
+ .ansh-compare__handle {
1422
+ position: absolute;
1423
+ top: 0;
1424
+ bottom: 0;
1425
+ z-index: 2;
1426
+ width: 2px;
1427
+ margin-left: -1px;
1428
+ background: var(--ansh-content-primary);
1429
+ cursor: ew-resize;
1430
+ touch-action: none;
1431
+ }
1432
+
1433
+ .ansh-compare__handle:focus-visible {
1434
+ outline: 2px solid var(--ansh-border-focus);
1435
+ outline-offset: 2px;
1436
+ }
1437
+
1438
+ .ansh-compare__grip {
1439
+ position: absolute;
1440
+ top: 50%;
1441
+ left: 50%;
1442
+ width: 2.25rem;
1443
+ height: 2.25rem;
1444
+ transform: translate(-50%, -50%);
1445
+ border-radius: var(--ansh-radius-full);
1446
+ background: var(--ansh-content-primary);
1447
+ box-shadow: var(--ansh-shadow-lg);
1448
+ }
1449
+
1450
+ /* The double chevron, drawn with borders so no icon asset or SVG is needed. */
1451
+ .ansh-compare__grip::before,
1452
+ .ansh-compare__grip::after {
1453
+ content: '';
1454
+ position: absolute;
1455
+ top: 50%;
1456
+ width: 0.4rem;
1457
+ height: 0.4rem;
1458
+ border-top: 2px solid var(--ansh-surface-canvas);
1459
+ border-right: 2px solid var(--ansh-surface-canvas);
1460
+ }
1461
+
1462
+ .ansh-compare__grip::before {
1463
+ left: 30%;
1464
+ transform: translate(0, -50%) rotate(-135deg);
1465
+ }
1466
+
1467
+ .ansh-compare__grip::after {
1468
+ right: 30%;
1469
+ transform: translate(0, -50%) rotate(45deg);
1470
+ }
1471
+
1472
+ .ansh-compare[data-dragging] {
1473
+ cursor: ew-resize;
1474
+ }
1475
+
1476
+ /* --- Device / browser mockup --------------------------------------------- */
1477
+
1478
+ .ansh-mock {
1479
+ display: flex;
1480
+ flex-direction: column;
1481
+ overflow: hidden;
1482
+ background: var(--ansh-surface-raised);
1483
+ border: 1px solid var(--ansh-border-default);
1484
+ box-shadow: var(--ansh-shadow-lg);
1485
+ }
1486
+
1487
+ .ansh-mock--browser,
1488
+ .ansh-mock--window {
1489
+ border-radius: var(--ansh-radius-lg);
1490
+ }
1491
+
1492
+ .ansh-mock__chrome {
1493
+ display: flex;
1494
+ align-items: center;
1495
+ gap: var(--ansh-space-3);
1496
+ padding: var(--ansh-space-3) var(--ansh-space-4);
1497
+ background: var(--ansh-surface-sunken);
1498
+ border-bottom: 1px solid var(--ansh-border-subtle);
1499
+ }
1500
+
1501
+ .ansh-mock__dots {
1502
+ display: flex;
1503
+ gap: var(--ansh-space-2);
1504
+ flex-shrink: 0;
1505
+ }
1506
+
1507
+ .ansh-mock__dots i {
1508
+ width: 0.7rem;
1509
+ height: 0.7rem;
1510
+ border-radius: var(--ansh-radius-full);
1511
+ background: var(--ansh-border-strong);
1512
+ }
1513
+
1514
+ .ansh-mock__url,
1515
+ .ansh-mock__title {
1516
+ flex: 1;
1517
+ min-width: 0;
1518
+ overflow: hidden;
1519
+ text-overflow: ellipsis;
1520
+ white-space: nowrap;
1521
+ font-size: var(--ansh-text-xs);
1522
+ color: var(--ansh-content-tertiary);
1523
+ }
1524
+
1525
+ .ansh-mock__url {
1526
+ padding: var(--ansh-space-1) var(--ansh-space-3);
1527
+ border-radius: var(--ansh-radius-full);
1528
+ background: var(--ansh-surface-raised);
1529
+ font-family: var(--ansh-font-mono);
1530
+ }
1531
+
1532
+ .ansh-mock__title {
1533
+ text-align: center;
1534
+ }
1535
+
1536
+ .ansh-mock__screen {
1537
+ flex: 1;
1538
+ min-height: 0;
1539
+ overflow: hidden;
1540
+ background: var(--ansh-surface-canvas);
1541
+ }
1542
+
1543
+ /* The phone frame: a thick bezel and a notch, no chrome bar. */
1544
+ .ansh-mock--phone {
1545
+ border-radius: 2.25rem;
1546
+ border-width: 0.6rem;
1547
+ border-color: var(--ansh-content-primary);
1548
+ padding: 0;
1549
+ position: relative;
1550
+ }
1551
+
1552
+ .ansh-mock--phone .ansh-mock__screen {
1553
+ border-radius: 1.7rem;
1554
+ }
1555
+
1556
+ .ansh-mock__notch {
1557
+ position: absolute;
1558
+ top: 0;
1559
+ left: 50%;
1560
+ transform: translateX(-50%);
1561
+ z-index: 2;
1562
+ width: 38%;
1563
+ height: 1.1rem;
1564
+ border-radius: 0 0 var(--ansh-radius-lg) var(--ansh-radius-lg);
1565
+ background: var(--ansh-content-primary);
1566
+ }
1567
+
1568
+ /* --- Card stack ----------------------------------------------------------- */
1569
+
1570
+ .ansh-stack {
1571
+ display: flex;
1572
+ flex-direction: column;
1573
+ gap: var(--ansh-space-4);
1574
+ }
1575
+
1576
+ .ansh-stack__deck {
1577
+ position: relative;
1578
+ display: grid;
1579
+ /* One cell: every card stacks in it, and the tallest card sets the height so the deck does not
1580
+ resize as cards advance. */
1581
+ grid-template-areas: 'card';
1582
+ isolation: isolate;
1583
+ }
1584
+
1585
+ .ansh-stack__card {
1586
+ grid-area: card;
1587
+ transition:
1588
+ transform var(--ansh-duration-slow, 420ms) var(--ansh-ease-emphasized),
1589
+ opacity var(--ansh-duration-slow, 420ms) var(--ansh-ease-standard);
1590
+ will-change: transform, opacity;
1591
+ }
1592
+
1593
+ .ansh-stack__controls {
1594
+ display: flex;
1595
+ align-items: center;
1596
+ justify-content: center;
1597
+ gap: var(--ansh-space-3);
1598
+ }
1599
+
1600
+ .ansh-stack__button {
1601
+ display: grid;
1602
+ place-items: center;
1603
+ width: 2rem;
1604
+ height: 2rem;
1605
+ border-radius: var(--ansh-radius-full);
1606
+ border: 1px solid var(--ansh-border-default);
1607
+ background: var(--ansh-surface-raised);
1608
+ color: var(--ansh-content-secondary);
1609
+ font: inherit;
1610
+ font-size: var(--ansh-text-lg);
1611
+ line-height: 1;
1612
+ cursor: pointer;
1613
+ }
1614
+
1615
+ .ansh-stack__button:hover {
1616
+ border-color: var(--ansh-border-strong);
1617
+ color: var(--ansh-content-primary);
1618
+ }
1619
+
1620
+ .ansh-stack__count {
1621
+ font-family: var(--ansh-font-mono);
1622
+ font-size: var(--ansh-text-xs);
1623
+ color: var(--ansh-content-tertiary);
1624
+ min-width: 3.5rem;
1625
+ text-align: center;
1626
+ }
1627
+
1628
+ /*
1629
+ * Reduced motion. The library's kernel handles JS-driven motion, but these components animate in
1630
+ * CSS, so they need their own guard — `@ansh-ui/tokens` neutralises durations globally, and this
1631
+ * additionally removes the transform travel so cards and words change in place rather than
1632
+ * snapping across a distance in 0.01ms.
1633
+ */
1634
+ @media (prefers-reduced-motion: reduce) {
1635
+ .ansh-rotate__word {
1636
+ animation: none !important;
1637
+ }
1638
+
1639
+ .ansh-stack__card {
1640
+ transition: none !important;
1641
+ }
1642
+ }
1643
+
1297
1644
  /* ==========================================================================
1298
1645
  Forms
1299
1646
  ========================================================================== */
@@ -5363,6 +5710,123 @@ button.ansh-stepper__button:hover {
5363
5710
  text-underline-offset: 2px;
5364
5711
  }
5365
5712
 
5713
+ /* --- Carousel ------------------------------------------------------------- */
5714
+
5715
+ .ansh-carousel {
5716
+ position: relative;
5717
+ display: flex;
5718
+ flex-direction: column;
5719
+ gap: var(--ansh-space-3);
5720
+ }
5721
+
5722
+ .ansh-carousel__viewport {
5723
+ position: relative;
5724
+ overflow: hidden;
5725
+ border-radius: var(--ansh-radius-lg);
5726
+ /* Without this a horizontal drag scrolls the page on touch instead of moving the track — the
5727
+ same defect the comparison slider guards against. */
5728
+ touch-action: pan-y;
5729
+ }
5730
+
5731
+ .ansh-carousel__track {
5732
+ display: flex;
5733
+ transition: transform var(--ansh-duration-slow, 420ms) var(--ansh-ease-emphasized);
5734
+ will-change: transform;
5735
+ }
5736
+
5737
+ .ansh-carousel__slide {
5738
+ /* `flex: 0 0 100%` rather than `width: 100%`: flex items shrink by default, so eight slides in a
5739
+ row would each collapse to an eighth of the viewport instead of queueing beside it. */
5740
+ flex: 0 0 100%;
5741
+ min-width: 0;
5742
+ }
5743
+
5744
+ .ansh-carousel__viewport[data-dragging] {
5745
+ cursor: grabbing;
5746
+ }
5747
+
5748
+ .ansh-carousel__arrow {
5749
+ position: absolute;
5750
+ top: calc(50% - 1.25rem);
5751
+ display: grid;
5752
+ place-items: center;
5753
+ width: 2.5rem;
5754
+ height: 2.5rem;
5755
+ border-radius: var(--ansh-radius-full);
5756
+ border: 1px solid var(--ansh-border-default);
5757
+ background: var(--ansh-surface-overlay);
5758
+ color: var(--ansh-content-primary);
5759
+ font: inherit;
5760
+ font-size: var(--ansh-text-xl);
5761
+ line-height: 1;
5762
+ cursor: pointer;
5763
+ }
5764
+
5765
+ .ansh-carousel__arrow:hover:not(:disabled) {
5766
+ border-color: var(--ansh-border-strong);
5767
+ }
5768
+
5769
+ .ansh-carousel__arrow:disabled {
5770
+ opacity: 0.4;
5771
+ cursor: default;
5772
+ }
5773
+
5774
+ .ansh-carousel__arrow--prev {
5775
+ left: var(--ansh-space-3);
5776
+ }
5777
+
5778
+ .ansh-carousel__arrow--next {
5779
+ right: var(--ansh-space-3);
5780
+ }
5781
+
5782
+ .ansh-carousel__controls {
5783
+ display: flex;
5784
+ align-items: center;
5785
+ justify-content: center;
5786
+ gap: var(--ansh-space-3);
5787
+ }
5788
+
5789
+ .ansh-carousel__dots {
5790
+ display: flex;
5791
+ gap: var(--ansh-space-2);
5792
+ }
5793
+
5794
+ .ansh-carousel__dot {
5795
+ width: 0.5rem;
5796
+ height: 0.5rem;
5797
+ padding: 0;
5798
+ border: 0;
5799
+ border-radius: var(--ansh-radius-full);
5800
+ background: var(--ansh-border-strong);
5801
+ cursor: pointer;
5802
+ transition: background-color var(--ansh-duration-fast) var(--ansh-ease-standard);
5803
+ }
5804
+
5805
+ .ansh-carousel__dot[aria-current] {
5806
+ background: var(--ansh-accent-default);
5807
+ }
5808
+
5809
+ .ansh-carousel__pause {
5810
+ display: grid;
5811
+ place-items: center;
5812
+ min-width: 1.75rem;
5813
+ height: 1.75rem;
5814
+ padding: 0 var(--ansh-space-2);
5815
+ border-radius: var(--ansh-radius-full);
5816
+ border: 1px solid var(--ansh-border-default);
5817
+ background: transparent;
5818
+ color: var(--ansh-content-secondary);
5819
+ font: inherit;
5820
+ font-size: var(--ansh-text-xs);
5821
+ cursor: pointer;
5822
+ }
5823
+
5824
+ @media (prefers-reduced-motion: reduce) {
5825
+ .ansh-carousel__track {
5826
+ transition: none !important;
5827
+ }
5828
+ }
5829
+
5366
5830
  /* ==========================================================================
5367
5831
  Feedback — alerts, banners, callouts
5368
5832
  ========================================================================== */
@@ -93,7 +93,16 @@ declare function useReducedMotion(): boolean;
93
93
  * ship in production render paths; the `enabled` flag exists so it can be gated on `NODE_ENV`.
94
94
  */
95
95
  declare function useFrameBudget(enabled?: boolean): BudgetReport | null;
96
+ /**
97
+ * Live count of components currently subscribed to the shared ticker.
98
+ *
99
+ * Intended for a devtools-style overlay, alongside `useFrameBudget` — the ticker only exposes
100
+ * `subscriberCount` as a getter with no change event, so this piggybacks on `onBudget`'s existing
101
+ * ~4 Hz sample instead of opening a second interval or rAF loop just to poll one number.
102
+ */
103
+ declare function useTickerSubscriberCount(enabled?: boolean): number;
96
104
  /** Merge several refs into one callback ref, for components that both forward and use a ref. */
97
105
  declare function useMergedRef<T>(...refs: Array<React.Ref<T> | undefined | null>): (node: T | null) => void;
98
106
 
99
- export { type InViewOptions as I, useFrameBudget as a, useInView as b, useMergedRef as c, usePointer as d, useReducedMotion as e, useScrollProgress as f, useTicker as g, useWindowScroll as h, useElementSize as u };
107
+ export { useFrameBudget as a, useInView as b, useMergedRef as c, usePointer as d, useReducedMotion as e, useScrollProgress as f, useTicker as g, useTickerSubscriberCount as h, useWindowScroll as i, useElementSize as u };
108
+ export type { InViewOptions as I };
@@ -93,7 +93,16 @@ declare function useReducedMotion(): boolean;
93
93
  * ship in production render paths; the `enabled` flag exists so it can be gated on `NODE_ENV`.
94
94
  */
95
95
  declare function useFrameBudget(enabled?: boolean): BudgetReport | null;
96
+ /**
97
+ * Live count of components currently subscribed to the shared ticker.
98
+ *
99
+ * Intended for a devtools-style overlay, alongside `useFrameBudget` — the ticker only exposes
100
+ * `subscriberCount` as a getter with no change event, so this piggybacks on `onBudget`'s existing
101
+ * ~4 Hz sample instead of opening a second interval or rAF loop just to poll one number.
102
+ */
103
+ declare function useTickerSubscriberCount(enabled?: boolean): number;
96
104
  /** Merge several refs into one callback ref, for components that both forward and use a ref. */
97
105
  declare function useMergedRef<T>(...refs: Array<React.Ref<T> | undefined | null>): (node: T | null) => void;
98
106
 
99
- export { type InViewOptions as I, useFrameBudget as a, useInView as b, useMergedRef as c, usePointer as d, useReducedMotion as e, useScrollProgress as f, useTicker as g, useWindowScroll as h, useElementSize as u };
107
+ export { useFrameBudget as a, useInView as b, useMergedRef as c, usePointer as d, useReducedMotion as e, useScrollProgress as f, useTicker as g, useTickerSubscriberCount as h, useWindowScroll as i, useElementSize as u };
108
+ export type { InViewOptions as I };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansh-ui/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Ansh for React and Next.js — premium animated components and motion hooks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -74,8 +74,8 @@
74
74
  "./package.json": "./package.json"
75
75
  },
76
76
  "dependencies": {
77
- "@ansh-ui/tokens": "0.2.0",
78
- "@ansh-ui/core": "0.2.0"
77
+ "@ansh-ui/core": "0.3.1",
78
+ "@ansh-ui/tokens": "0.3.1"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "react": "^18.2.0 || ^19.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-observers.ts"],"names":["useRef","useEffect","getTicker","useState","observeIntersection","observeResize","trackScroll","trackContainerScroll","trackPointer","onReducedMotionChange","useCallback"],"mappings":"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","file":"chunk-6VRF7X5C.cjs","sourcesContent":["import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'\nimport {\n type BudgetReport,\n type FrameState,\n type PointerState,\n type ScrollProgress,\n type ScrollTrackOptions,\n getTicker,\n observeIntersection,\n observeResize,\n onReducedMotionChange,\n trackContainerScroll,\n trackPointer,\n trackScroll,\n} from '@ansh-ui/core'\n\n/**\n * Subscribe to the shared ticker.\n *\n * The callback is held in a ref and never re-subscribes, so a component that closes over changing\n * props doesn't tear down and rebuild its subscription on every render. That matters: resubscribing\n * changes execution order within the phase, which can visibly reorder dependent animations.\n */\nexport function useTicker(\n callback: (state: FrameState) => void,\n options: {\n phase?: 'read' | 'compute' | 'write' | 'post'\n priority?: number\n enabled?: boolean\n } = {},\n): void {\n const callbackRef = useRef(callback)\n callbackRef.current = callback\n\n const { phase = 'write', priority = 0, enabled = true } = options\n\n useEffect(() => {\n if (!enabled) return\n const ticker = getTicker()\n return ticker.add((state) => callbackRef.current(state), { phase, priority, name: 'useTicker' })\n }, [phase, priority, enabled])\n}\n\nexport interface InViewOptions {\n /** Fraction visible before triggering, in `[0, 1]`. Default `0`. */\n threshold?: number\n /** Extra margin around the root. Default `'0px'`. */\n rootMargin?: string\n /** Stop observing after the first intersection. Default `false`. */\n once?: boolean\n root?: Element | null\n}\n\n/**\n * Is the element in the viewport?\n *\n * Uses the pooled observer, so a page with 200 scroll-revealed elements creates one\n * `IntersectionObserver` rather than 200.\n */\nexport function useInView<El extends Element = HTMLDivElement>(\n options: InViewOptions = {},\n): [RefObject<El | null>, boolean] {\n const ref = useRef<El | null>(null)\n const [inView, setInView] = useState(false)\n\n const { threshold = 0, rootMargin = '0px', once = false, root = null } = options\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n\n let released = false\n const stop = observeIntersection(\n element,\n (entry) => {\n setInView(entry.isIntersecting)\n if (entry.isIntersecting && once && !released) {\n released = true\n // Release inside a microtask: unsubscribing from within the observer callback would\n // mutate the pool's callback set while it is being iterated.\n queueMicrotask(() => stop())\n }\n },\n { threshold, rootMargin, root },\n )\n\n return () => {\n released = true\n stop()\n }\n }, [threshold, rootMargin, once, root])\n\n return [ref, inView]\n}\n\n/**\n * Track an element's size.\n *\n * Returns state rather than a MotionValue because size changes at interaction rate, not frame\n * rate, so a re-render per change is appropriate and far more ergonomic.\n */\nexport function useElementSize<El extends Element = HTMLDivElement>(): [\n RefObject<El | null>,\n { width: number; height: number },\n] {\n const ref = useRef<El | null>(null)\n const [size, setSize] = useState({ width: 0, height: 0 })\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n return observeResize(element, ({ width, height }) => {\n // Guard against redundant renders: ResizeObserver fires on sub-pixel changes during\n // animation, and setting identical state still costs a render pass in React 18.\n setSize((previous) =>\n previous.width === width && previous.height === height ? previous : { width, height },\n )\n })\n }, [])\n\n return [ref, size]\n}\n\n/**\n * Track an element's scroll progress through the viewport.\n *\n * The returned progress is a **ref**, not state, deliberately — scroll progress changes every\n * frame, and rendering on each change would make scrolling janky. Read `progress.current` inside a\n * `useTicker` callback, or pass the `onChange` callback and write to the DOM directly.\n */\nexport function useScrollProgress<El extends HTMLElement = HTMLDivElement>(\n options: ScrollTrackOptions & { onChange?: (progress: ScrollProgress) => void } = {},\n): [RefObject<El | null>, RefObject<ScrollProgress>] {\n const ref = useRef<El | null>(null)\n const progressRef = useRef<ScrollProgress>({\n progress: 0,\n velocity: 0,\n scroll: 0,\n direction: 0,\n inView: false,\n })\n\n const onChangeRef = useRef(options.onChange)\n onChangeRef.current = options.onChange\n\n const { axis = 'y', container = null } = options\n const offsetKey = JSON.stringify(options.offset ?? null)\n\n useEffect(() => {\n const element = ref.current\n if (!element) return\n\n return trackScroll(\n element,\n (progress) => {\n progressRef.current = progress\n onChangeRef.current?.(progress)\n },\n {\n axis,\n container,\n ...(options.offset ? { offset: options.offset } : {}),\n },\n )\n // `offsetKey` stands in for the offset object, which is usually an inline literal and would\n // otherwise change identity on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [axis, container, offsetKey])\n\n return [ref, progressRef]\n}\n\n/** Track the window's (or a container's) overall scroll progress. */\nexport function useWindowScroll(\n onChange?: (info: { progress: number; scroll: number; velocity: number; max: number }) => void,\n options: { axis?: 'y' | 'x'; container?: HTMLElement | null } = {},\n): RefObject<{ progress: number; scroll: number; velocity: number; max: number }> {\n const stateRef = useRef({ progress: 0, scroll: 0, velocity: 0, max: 1 })\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n\n const { axis = 'y', container = null } = options\n\n useEffect(\n () =>\n trackContainerScroll(\n (info) => {\n stateRef.current = info\n onChangeRef.current?.(info)\n },\n { axis, container },\n ),\n [axis, container],\n )\n\n return stateRef\n}\n\n/**\n * Track pointer position over an element.\n *\n * Returns a ref to the live state object, which is mutated in place by the tracker. Reading it\n * inside a `useTicker` callback is the intended use; it deliberately does not trigger renders.\n */\nexport function usePointer<El extends HTMLElement = HTMLDivElement>(\n options: { smoothing?: number; trackOutside?: boolean; enabled?: boolean } = {},\n): [RefObject<El | null>, RefObject<Readonly<PointerState> | null>] {\n const ref = useRef<El | null>(null)\n const stateRef = useRef<Readonly<PointerState> | null>(null)\n\n const { smoothing, trackOutside = false, enabled = true } = options\n\n useEffect(() => {\n const element = ref.current\n if (!element || !enabled) return\n\n const tracker = trackPointer(element, {\n ...(smoothing !== undefined ? { smoothing } : {}),\n trackOutside,\n })\n stateRef.current = tracker.state\n\n return () => {\n tracker.destroy()\n stateRef.current = null\n }\n }, [smoothing, trackOutside, enabled])\n\n return [ref, stateRef]\n}\n\n/**\n * Does the user prefer reduced motion?\n *\n * Returns `false` during SSR and on the first client render, then updates. That ordering is\n * deliberate: returning the real value during SSR is impossible (the server cannot know), and\n * returning it on the first client render would be a hydration mismatch.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n useEffect(() => onReducedMotionChange(setReduced), [])\n return reduced\n}\n\n/**\n * Live frame-budget telemetry — FPS, frame cost percentiles, and the adaptive quality scalar.\n *\n * Intended for a development overlay. Subscribing causes a re-render every frame, so this must not\n * ship in production render paths; the `enabled` flag exists so it can be gated on `NODE_ENV`.\n */\nexport function useFrameBudget(enabled = true): BudgetReport | null {\n const [report, setReport] = useState<BudgetReport | null>(null)\n\n useEffect(() => {\n if (!enabled) return\n const ticker = getTicker()\n // Sample at ~4 Hz rather than per frame. A number that changes 60 times a second is\n // unreadable, and rendering it 60 times a second distorts the very measurement being shown.\n let last = 0\n return ticker.onBudget((next) => {\n const now = performance.now()\n if (now - last < 250) return\n last = now\n setReport(next)\n })\n }, [enabled])\n\n return report\n}\n\n/** Merge several refs into one callback ref, for components that both forward and use a ref. */\nexport function useMergedRef<T>(\n ...refs: Array<React.Ref<T> | undefined | null>\n): (node: T | null) => void {\n return useCallback((node: T | null) => {\n for (const ref of refs) {\n if (!ref) continue\n if (typeof ref === 'function') ref(node)\n else (ref as React.MutableRefObject<T | null>).current = node\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, refs)\n}\n"]}