@astryxdesign/lab 0.7.5-canary.fccd771 → 0.7.6-canary.bb18c81

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 (95) hide show
  1. package/README.md +2 -2
  2. package/astryx.integration.mjs +1 -1
  3. package/blocks/CommentPinShowcase.doc.mjs +1 -1
  4. package/blocks/CommentPinShowcase.tsx +2 -3
  5. package/dist/Chat/ChatUnreadDivider.js +1 -1
  6. package/dist/CircularProgress/CircularProgress.d.ts +2 -1
  7. package/dist/CircularProgress/CircularProgress.d.ts.map +1 -1
  8. package/dist/CircularProgress/CircularProgress.js +296 -19
  9. package/dist/CommentPin/CommentPin.d.ts +22 -7
  10. package/dist/CommentPin/CommentPin.d.ts.map +1 -1
  11. package/dist/CommentPin/CommentPin.js +115 -38
  12. package/dist/Comments/icons.d.ts.map +1 -1
  13. package/dist/Comments/icons.js +9 -2
  14. package/dist/DiffViewer/DiffViewer.d.ts +15 -4
  15. package/dist/DiffViewer/DiffViewer.d.ts.map +1 -1
  16. package/dist/DiffViewer/DiffViewer.js +31 -6
  17. package/dist/EmojiPicker/EmojiPicker.js +5 -8
  18. package/dist/InfoTip/InfoTip.d.ts +1 -1
  19. package/dist/InfoTip/InfoTip.js +1 -1
  20. package/dist/ListInput/ListInput.d.ts +1 -1
  21. package/dist/ListInput/ListInput.d.ts.map +1 -1
  22. package/dist/ListInput/ListInput.js +20 -1
  23. package/dist/LogStream/LogStream.d.ts.map +1 -1
  24. package/dist/LogStream/LogStream.js +2 -2
  25. package/dist/MobilePowerSearch/MobilePowerSearch.d.ts +49 -18
  26. package/dist/MobilePowerSearch/MobilePowerSearch.d.ts.map +1 -1
  27. package/dist/MobilePowerSearch/MobilePowerSearch.js +934 -366
  28. package/dist/MobilePowerSearch/MobilePowerSearchValueEditor.d.ts +10 -16
  29. package/dist/MobilePowerSearch/MobilePowerSearchValueEditor.d.ts.map +1 -1
  30. package/dist/MobilePowerSearch/MobilePowerSearchValueEditor.js +218 -113
  31. package/dist/MobilePowerSearch/mobilePowerSearchUtils.d.ts +23 -1
  32. package/dist/MobilePowerSearch/mobilePowerSearchUtils.d.ts.map +1 -1
  33. package/dist/MobilePowerSearch/mobilePowerSearchUtils.js +62 -2
  34. package/dist/MobileTokenizer/MobileTokenizer.d.ts +35 -10
  35. package/dist/MobileTokenizer/MobileTokenizer.d.ts.map +1 -1
  36. package/dist/MobileTokenizer/MobileTokenizer.js +714 -305
  37. package/dist/SVGIcon/SVGIcon.js +3 -3
  38. package/dist/Stat/Stat.js +2 -2
  39. package/dist/Tour/Tour.d.ts +1 -1
  40. package/dist/Tour/Tour.js +1 -1
  41. package/dist/Tour/TourStep.d.ts +1 -1
  42. package/dist/Tour/TourStep.js +1 -1
  43. package/dist/TransferList/TransferList.d.ts.map +1 -1
  44. package/dist/TransferList/TransferList.js +20 -0
  45. package/dist/index.js +3 -3
  46. package/dist/lab.css +43 -23
  47. package/dist/reorderStyles.d.ts +2 -2
  48. package/dist/reorderStyles.d.ts.map +1 -1
  49. package/dist/reorderStyles.js +2 -2
  50. package/package.json +10 -10
  51. package/src/Chat/ChatUnreadDivider.tsx +1 -1
  52. package/src/Chat/__tests__/ChatTypingIndicator.a11y.chromium.spec.ts +21 -8
  53. package/src/CircularProgress/CircularProgress.test.tsx +389 -10
  54. package/src/CircularProgress/CircularProgress.tsx +367 -23
  55. package/src/CircularProgress/__tests__/CircularProgress.a11y.chromium.spec.ts +22 -9
  56. package/src/CircularProgress/__tests__/CircularProgress.a11y.states.ts +2 -2
  57. package/src/CircularProgress/__tests__/CircularProgressMotion.a11y.chromium.spec.ts +886 -0
  58. package/src/CommentPin/CommentPin.doc.mjs +82 -34
  59. package/src/CommentPin/CommentPin.spec.md +92 -81
  60. package/src/CommentPin/CommentPin.test.tsx +98 -19
  61. package/src/CommentPin/CommentPin.tsx +146 -82
  62. package/src/Comments/Comments.test.tsx +10 -4
  63. package/src/Comments/icons.test.tsx +76 -0
  64. package/src/Comments/icons.tsx +9 -2
  65. package/src/DiffViewer/DiffViewer.doc.mjs +8 -2
  66. package/src/DiffViewer/DiffViewer.test.tsx +71 -0
  67. package/src/DiffViewer/DiffViewer.tsx +52 -9
  68. package/src/EmojiPicker/EmojiPicker.test.tsx +21 -0
  69. package/src/EmojiPicker/EmojiPicker.tsx +8 -8
  70. package/src/InfoTip/InfoTip.test.tsx +9 -4
  71. package/src/InfoTip/InfoTip.tsx +1 -1
  72. package/src/ListInput/ListInput.test.tsx +43 -3
  73. package/src/ListInput/ListInput.tsx +24 -1
  74. package/src/LogStream/LogStream.doc.mjs +1 -1
  75. package/src/LogStream/LogStream.tsx +3 -5
  76. package/src/MobilePowerSearch/MobilePowerSearch.doc.mjs +5 -5
  77. package/src/MobilePowerSearch/MobilePowerSearch.test.tsx +1092 -167
  78. package/src/MobilePowerSearch/MobilePowerSearch.tsx +1094 -368
  79. package/src/MobilePowerSearch/MobilePowerSearchValueEditor.tsx +303 -120
  80. package/src/MobilePowerSearch/mobilePowerSearchUtils.ts +68 -1
  81. package/src/MobileTokenizer/MobileTokenizer.doc.mjs +9 -5
  82. package/src/MobileTokenizer/MobileTokenizer.test.tsx +589 -5
  83. package/src/MobileTokenizer/MobileTokenizer.tsx +889 -350
  84. package/src/SVGIcon/SVGIcon.tsx +3 -3
  85. package/src/Stat/Stat.tsx +2 -2
  86. package/src/Tour/Tour.doc.mjs +1 -1
  87. package/src/Tour/Tour.tsx +1 -1
  88. package/src/Tour/TourStep.tsx +1 -1
  89. package/src/TransferList/TransferList.composition.test.tsx +4 -4
  90. package/src/TransferList/TransferList.test.tsx +36 -0
  91. package/src/TransferList/TransferList.tsx +24 -0
  92. package/src/TransferList/TransferListSelector.test.tsx +4 -4
  93. package/src/__tests__/ReorderGripPinchZoom.a11y.chromium.spec.ts +178 -0
  94. package/src/index.ts +3 -3
  95. package/src/reorderStyles.ts +7 -2
package/README.md CHANGED
@@ -12,7 +12,7 @@ Components in lab:
12
12
  - Compose with `@astryxdesign/core` components (use the theme, follow naming conventions)
13
13
  - Graduate to `@astryxdesign/core` after thorough engineering review
14
14
 
15
- See the **[Component Lifecycle](https://github.com/facebook/astryx/wiki/Component-Lifecycle)** wiki for how a component moves from lab → core (and the promotion gates), and the **[Component Hardening Protocol](https://github.com/facebook/astryx/wiki/Component-Hardening-Protocol)** for the bar a component must clear to graduate.
15
+ See the **[Component Lifecycle](https://github.com/meta/astryx/wiki/Component-Lifecycle)** wiki for how a component moves from lab → core (and the promotion gates), and the **[Component Hardening Protocol](https://github.com/meta/astryx/wiki/Component-Hardening-Protocol)** for the bar a component must clear to graduate.
16
16
 
17
17
  ## What's here vs what's in core
18
18
 
@@ -22,7 +22,7 @@ See the **[Component Lifecycle](https://github.com/facebook/astryx/wiki/Componen
22
22
 
23
23
  ## Promotion gate: accessibility
24
24
 
25
- The [Accessibility Checklist](https://github.com/facebook/astryx/wiki/Accessibility-Checklist) is a hard requirement for graduating to core — every item, verified in the promotion PR, alongside the hardening bar linked above.
25
+ The [Accessibility Checklist](https://github.com/meta/astryx/wiki/Accessibility-Checklist) is a hard requirement for graduating to core — every item, verified in the promotion PR, alongside the hardening bar linked above.
26
26
 
27
27
  Lab components get the same scan coverage as core (the `pr-a11y` axe audit and the weekly full scan run against lab too), but a11y findings on a lab component do not block lab merges — they block **promotion**. Iterate freely in lab; clear the checklist to graduate.
28
28
 
@@ -7,5 +7,5 @@
7
7
  export default {
8
8
  components: './src',
9
9
  templates: './blocks',
10
- issuesUrl: 'https://github.com/facebook/astryx/issues',
10
+ issuesUrl: 'https://github.com/meta/astryx/issues',
11
11
  };
@@ -6,7 +6,7 @@ export default {
6
6
  name: 'Avatar-first annotations',
7
7
  displayName: 'Avatar-first annotations',
8
8
  description:
9
- 'Caller-positioned comment pins over media and in a content margin. Every coordinate maps to the pin’s logical bottom-start corner. The selected group caps its participants at three circles (two Avatars plus +3) and shows its unread count in an accent badge instead of the total; the unread margin pin shows the badge too. Other pins dim without becoming disabled, activating the selected pin again clears the selection, and no pin dims while nothing is selected.',
9
+ 'Caller-positioned comment pins over media and in a content margin. Every coordinate maps to the pin’s logical bottom-start corner. The selected group caps its participants at three circles (two Avatars plus an ellipsis) and ends with its total and an accent unread dot; the unread margin pin shows the dot too. Other pins dim without becoming disabled, activating the selected pin again clears the selection, and no pin dims while nothing is selected.',
10
10
  exampleFor: 'CommentPin',
11
11
  isReady: true,
12
12
  isShowcase: true,
@@ -7,7 +7,7 @@
7
7
  * participant/thread/unread counts, and controlled selected/dimmed state
8
8
  * that may be empty
9
9
  * @output A grouped pin with a three-circle participant cap and an unread
10
- * badge, a resolved pin, and an unread margin pin; activating the selected
10
+ * dot, a single pin, and an unread margin pin; activating the selected
11
11
  * pin clears the selection, and non-selected pins dim only while another pin
12
12
  * is selected
13
13
  * @position CommentPin docsite showcase and copyable Lab block
@@ -160,9 +160,8 @@ export default function CommentPinShowcase() {
160
160
  xstyle={styles.groupedMarker}
161
161
  />
162
162
  <CommentPin
163
- label="Open resolved annotation from Mira"
163
+ label="Open annotation from Mira"
164
164
  participants={[PEOPLE.mira]}
165
- isResolved
166
165
  isSelected={selected === 'resolved'}
167
166
  isDimmed={selected != null && selected !== 'resolved'}
168
167
  onClick={() => toggle('resolved')}
@@ -74,7 +74,7 @@ export function ChatUnreadDivider({
74
74
  }
75
75
  }), /*#__PURE__*/_jsx("span", {
76
76
  ...{
77
- className: "x2lah0s x9ynric x141an7d x1lvx875 xjt36v0 x1ltkj2j"
77
+ className: "x2lah0s x9ynric x141an7d x1lvx875 x1wj83wy x1ltkj2j"
78
78
  },
79
79
  children: label
80
80
  })]
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @file CircularProgress.tsx
3
- * @input Uses React, useId, stylex, SVG, color/spacing/duration/ease tokens
3
+ * @input Uses React (useId, refs, layout effects), stylex, SVG, Web
4
+ * Animations, color/spacing/duration/ease tokens
4
5
  * @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
5
6
  * @position Core implementation; consumed by index.ts
6
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAY5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAQtD,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACtE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAS,EACT,GAAS,EACT,KAAK,EACL,aAAoB,EACpB,aAAqB,EACrB,gBAA0C,EAC1C,QAAQ,EACR,IAAW,EACX,OAAkB,EAClB,eAAuB,EACvB,UAAkB,EAClB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAU,EACzB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,qBAAqB,+BAuHvB;yBAzIe,gBAAgB"}
1
+ {"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAYrE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAWtD,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACtE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmWD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAS,EACT,GAAS,EACT,KAAK,EACL,aAAoB,EACpB,aAAqB,EACrB,gBAA0C,EAC1C,QAAQ,EACR,IAAW,EACX,OAAkB,EAClB,eAAuB,EACvB,UAAkB,EAClB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAU,EACzB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,qBAAqB,+BA+QvB;yBAjSe,gBAAgB"}
@@ -4,7 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file CircularProgress.tsx
7
- * @input Uses React, useId, stylex, SVG, color/spacing/duration/ease tokens
7
+ * @input Uses React (useId, refs, layout effects), stylex, SVG, Web
8
+ * Animations, color/spacing/duration/ease tokens
8
9
  * @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
9
10
  * @position Core implementation; consumed by index.ts
10
11
  *
@@ -14,7 +15,7 @@
14
15
  * - /packages/lab/src/CircularProgress/index.ts (exports if types change)
15
16
  * - /apps/storybook/stories/CircularProgress.stories.tsx (storybook stories)
16
17
  */
17
- import { useId } from 'react';
18
+ import { useId, useLayoutEffect, useRef } from 'react';
18
19
  import * as stylex from '@stylexjs/stylex';
19
20
  import '@astryxdesign/core/theme/tokens.stylex';
20
21
  import { colorVars, durationVars, easeVars, fontWeightVars, spacingVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
@@ -35,6 +36,8 @@ import { themeProps } from '@astryxdesign/core/utils';
35
36
  * ```
36
37
  */
37
38
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
39
+ /** How far a turning layer's drawing reaches past the ring's box, in px. */
40
+ const FILL_BLEED = 2;
38
41
  const SIZE_CONFIG = {
39
42
  sm: {
40
43
  diameter: 32,
@@ -83,9 +86,11 @@ const styles = {
83
86
  $$css: true
84
87
  },
85
88
  fill: {
86
- kDwRjp: "xbh8q5q",
87
- kU5bRw: "x1owpc8m",
88
- k1ekBW: "xxnu56j",
89
+ kVAEAm: "x10l6tqk",
90
+ kpwlN0: "x10a8y8t",
91
+ kVQacm: "xb3r6kr",
92
+ kfzvcC: "x47corl",
93
+ k1ekBW: "x13b0p5u",
89
94
  kIyJzY: "x80gvsz",
90
95
  kAMwcw: "xlr8y92",
91
96
  $$css: true
@@ -106,15 +111,15 @@ const variantStyles = {
106
111
  $$css: true
107
112
  },
108
113
  success: {
109
- kjVXCG: "x8y33gb",
114
+ kjVXCG: "x88o7k4",
110
115
  $$css: true
111
116
  },
112
117
  warning: {
113
- kjVXCG: "x9ezeq1",
118
+ kjVXCG: "x16d96h5",
114
119
  $$css: true
115
120
  },
116
121
  error: {
117
- kjVXCG: "x1vco6zm",
122
+ kjVXCG: "xqb94c9",
118
123
  $$css: true
119
124
  },
120
125
  neutral: {
@@ -153,6 +158,129 @@ function defaultFormatValueLabel(value, max) {
153
158
  return `${pct}%`;
154
159
  }
155
160
 
161
+ /**
162
+ * Each half-ring's turn and the leading cap's for a filled share `p` (0–1),
163
+ * and which parts show. A half-ring parked at the edge of its half (the right
164
+ * one at 0, the left one until the value passes half) is hidden, so its
165
+ * antialiased end never leaves a hairline at 6 o'clock.
166
+ */
167
+ function fillTurns(p) {
168
+ const turn = 360 * p;
169
+ return {
170
+ right: Math.min(Math.max(turn, 0), 180),
171
+ left: Math.min(Math.max(turn, 180), 360),
172
+ cap: turn,
173
+ rightShown: p > 0 ? 1 : 0,
174
+ leftShown: p > 0.5 ? 1 : 0,
175
+ caps: p > 0 ? 1 : 0
176
+ };
177
+ }
178
+
179
+ /** A CSS time (`410ms`, `0.41s`, or a list's first entry) in milliseconds. */
180
+ function parseTime(value) {
181
+ const first = value.split(',')[0]?.trim() ?? '';
182
+ const amount = Number.parseFloat(first);
183
+ if (!Number.isFinite(amount)) {
184
+ return 0;
185
+ }
186
+ return first.endsWith('ms') ? amount : amount * 1000;
187
+ }
188
+
189
+ /**
190
+ * The theme's easing as a function of time (0–1), evaluated by the browser
191
+ * itself through a detached animation, so any CSS easing works. It runs only
192
+ * where Web Animations exists (its caller returns first otherwise), on the
193
+ * fill's computed `transition-timing-function`: one easing, which a browser
194
+ * always reports as valid CSS.
195
+ */
196
+ function easingOf(easing) {
197
+ const effect = new KeyframeEffect(null, null, {
198
+ duration: 1000,
199
+ easing,
200
+ fill: 'both'
201
+ });
202
+ const probe = new Animation(effect);
203
+ return t => {
204
+ probe.currentTime = t * 1000;
205
+ return effect.getComputedTiming().progress ?? t;
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Keyframes for a change from `from` to `to` (filled shares), sampled once a
211
+ * frame on the theme's easing, with an extra sample exactly where the arc
212
+ * crosses 12 or 6 o'clock so each half stops and starts on time.
213
+ */
214
+ function fillKeyframes(from, to, duration, ease) {
215
+ const count = Math.max(8, Math.ceil(duration / (1000 / 60)));
216
+ const grid = Array.from({
217
+ length: count + 1
218
+ }, (_, i) => i / count);
219
+ const at = t => from + (to - from) * ease(t);
220
+ // Where the eased path passes half (p = 0.5): a sample there, and one just
221
+ // after it, so the halves hand over on time and the left half's
222
+ // visibility steps rather than fades.
223
+ const crossings = [];
224
+ for (let i = 0; i < count; i++) {
225
+ const a = grid[i];
226
+ const b = grid[i + 1];
227
+ const fa = at(a) - 0.5;
228
+ const fb = at(b) - 0.5;
229
+ if (fa === 0) {
230
+ crossings.push(a);
231
+ } else if (fa * fb < 0) {
232
+ let low = a;
233
+ let high = b;
234
+ for (let k = 0; k < 24; k++) {
235
+ const mid = (low + high) / 2;
236
+ if ((at(low) - 0.5) * (at(mid) - 0.5) <= 0) {
237
+ high = mid;
238
+ } else {
239
+ low = mid;
240
+ }
241
+ }
242
+ crossings.push((low + high) / 2);
243
+ }
244
+ }
245
+ const times = [...new Set([...grid, ...crossings, ...crossings.map(c => c + 1e-4).filter(t => t < 1)])].sort((x, y) => x - y);
246
+ const frames = times.map(t => ({
247
+ t,
248
+ ...fillTurns(t === 1 ? to : at(t))
249
+ }));
250
+ // The sample at a crossing sits exactly on half, where the left half reads
251
+ // as hidden. Give it the side the arc is leaving instead, so the left half
252
+ // steps at once both ways: shrinking through half, it used to fade out
253
+ // from the sample before, while it still drew part of the arc (a lighter
254
+ // band between the end cap and 6 o'clock for a frame).
255
+ const onHalf = new Set(crossings);
256
+ for (let i = 1; i < frames.length; i += 1) {
257
+ if (onHalf.has(frames[i].t) || frames[i].t === 1 && to === 0.5) {
258
+ frames[i].leftShown = frames[i - 1].leftShown;
259
+ }
260
+ }
261
+ return {
262
+ right: frames.map(f => ({
263
+ offset: f.t,
264
+ transform: `rotate(${f.right}deg)`,
265
+ opacity: f.rightShown
266
+ })),
267
+ left: frames.map(f => ({
268
+ offset: f.t,
269
+ transform: `rotate(${f.left}deg)`,
270
+ opacity: f.leftShown
271
+ })),
272
+ cap: frames.map(f => ({
273
+ offset: f.t,
274
+ transform: `rotate(${f.cap}deg)`,
275
+ opacity: f.caps
276
+ })),
277
+ start: frames.map(f => ({
278
+ offset: f.t,
279
+ opacity: f.caps
280
+ }))
281
+ };
282
+ }
283
+
156
284
  /**
157
285
  * A circular/radial progress indicator that shows completion as a ring.
158
286
  *
@@ -205,7 +333,6 @@ export function CircularProgress({
205
333
  const safeMax = Number.isFinite(max) ? max : 0;
206
334
  const clampedValue = Math.min(Math.max(0, safeValue), safeMax);
207
335
  const percentage = safeMax > 0 ? clampedValue / safeMax : 0;
208
- const dashoffset = circumference * (1 - percentage);
209
336
  const valueText = formatValueLabel(clampedValue, safeMax);
210
337
  const center = diameter / 2;
211
338
  const showLabel = !isLabelHidden;
@@ -213,6 +340,89 @@ export function CircularProgress({
213
340
  const centerContent = children != null ? children : showValueLabel ? valueText : null;
214
341
  const fillVariant = isDisabled ? 'disabled' : variant;
215
342
  const trackVariant = isDisabled ? 'neutral' : variant;
343
+
344
+ // A value change turns the fill's layers with Web Animations on the
345
+ // compositor, from wherever the arc is drawn now (mid-change, the running
346
+ // change's own curve says where) to the new value, on the theme's
347
+ // duration and easing. What renders is always the resting state, so a
348
+ // first render, a render without script, or a skipped animation shows the
349
+ // value.
350
+ const fillRef = useRef(null);
351
+ const rightRef = useRef(null);
352
+ const leftRef = useRef(null);
353
+ const capRef = useRef(null);
354
+ const startRef = useRef(null);
355
+ // Where the arc is drawn: the value at rest, or, when a change is
356
+ // interrupted, the point its own curve has reached. A change's cleanup
357
+ // records it and releases the change's animations (react-component-runtime
358
+ // INV3), so the next change starts from there and an unmount or a switch to
359
+ // indeterminate leaves nothing running. While indeterminate no value is
360
+ // drawn (null): the ring draws its own arc, not the value it holds, so the
361
+ // next value draws at once instead of sweeping from that held value.
362
+ const drawn = useRef(percentage);
363
+ useLayoutEffect(() => {
364
+ const from = drawn.current;
365
+ if (isIndeterminate) {
366
+ drawn.current = null;
367
+ return;
368
+ }
369
+ if (from === null || from === percentage) {
370
+ drawn.current = percentage;
371
+ return;
372
+ }
373
+ const fill = fillRef.current;
374
+ const layers = [rightRef.current, leftRef.current, capRef.current, startRef.current];
375
+ drawn.current = percentage;
376
+ if (fill == null || layers.some(layer => typeof layer?.animate !== 'function')) {
377
+ return;
378
+ }
379
+ const style = getComputedStyle(fill);
380
+ const duration = parseTime(style.transitionDuration);
381
+ if (duration <= 0) {
382
+ return;
383
+ }
384
+ const ease = easingOf(style.transitionTimingFunction);
385
+ const frames = fillKeyframes(from, percentage, duration, ease);
386
+ const timing = {
387
+ duration,
388
+ easing: 'linear',
389
+ fill: 'backwards'
390
+ };
391
+ const [right, left, cap, start] = layers;
392
+ const animations = [right.animate(frames.right, timing), left.animate(frames.left, timing), cap.animate(frames.cap, timing), start.animate(frames.start, timing)];
393
+ return () => {
394
+ const [first] = animations;
395
+ const progress = first.playState !== 'finished' ? Number(first.effect?.getComputedTiming().progress ?? 1) : 1;
396
+ // `progress` is the change's linear time fraction; the arc is drawn on
397
+ // its easing, so the eased position is where it is.
398
+ drawn.current = from + (percentage - from) * ease(progress);
399
+ for (const animation of animations) {
400
+ animation.cancel();
401
+ }
402
+ };
403
+ }, [percentage, isIndeterminate]);
404
+ const turns = fillTurns(percentage);
405
+ // One half of the ring's stroke, from 6 o'clock clockwise to 12 (the left
406
+ // half) before its layer turns; butt ends, the caps are drawn apart.
407
+ const bleedBox = `${-FILL_BLEED} ${-FILL_BLEED} ${diameter + 2 * FILL_BLEED} ${diameter + 2 * FILL_BLEED}`;
408
+ const halfRing = /*#__PURE__*/_jsx("svg", {
409
+ width: diameter + 2 * FILL_BLEED,
410
+ height: diameter + 2 * FILL_BLEED,
411
+ viewBox: bleedBox,
412
+ ...{
413
+ className: "x1lliihq xbh8q5q xy6n8ys"
414
+ },
415
+ children: /*#__PURE__*/_jsx("circle", {
416
+ cx: center,
417
+ cy: center,
418
+ r: radius,
419
+ strokeWidth: strokeWidth,
420
+ strokeDasharray: `${circumference / 2} ${circumference}`,
421
+ transform: `rotate(90 ${center} ${center})`
422
+ })
423
+ });
424
+ // A round cap at 12 o'clock on the ring's centerline (a zero-length stroke).
425
+ const capPath = `M ${center} ${strokeWidth / 2} h 0`;
216
426
  return /*#__PURE__*/_jsxs("div", {
217
427
  ref: ref,
218
428
  ...mergeProps(themeProps('circular-progress', {
@@ -274,16 +484,83 @@ export function CircularProgress({
274
484
  cy: center,
275
485
  r: radius,
276
486
  strokeWidth: strokeWidth
277
- }) : /*#__PURE__*/_jsx("circle", {
278
- ...mergeProps(themeProps('circular-progress-fill', {
279
- variant: fillVariant
280
- }), stylex.props(styles.fill, variantStyles[fillVariant])),
281
- cx: center,
282
- cy: center,
283
- r: radius,
284
- strokeWidth: strokeWidth,
285
- strokeDasharray: circumference,
286
- strokeDashoffset: dashoffset
487
+ }) : null]
488
+ }), isIndeterminate ? null :
489
+ /*#__PURE__*/
490
+ // The determinate fill (see `styles.fill`). Its stroke color is
491
+ // set on the theming target and inherited by every part.
492
+ _jsxs("span", {
493
+ ref: fillRef,
494
+ "aria-hidden": "true",
495
+ ...mergeProps(themeProps('circular-progress-fill', {
496
+ variant: fillVariant
497
+ }), stylex.props(styles.fill, variantStyles[fillVariant])),
498
+ children: [/*#__PURE__*/_jsx("span", {
499
+ ...{
500
+ className: "x10l6tqk x13vifvy x3hqpx7 x5yr21d xb3r6kr x1nrll8i"
501
+ },
502
+ children: /*#__PURE__*/_jsx("span", {
503
+ ref: rightRef,
504
+ ...{
505
+ className: "x10l6tqk x13vifvy x75ztot x5yr21d x1q1rmc8 xb5nubl"
506
+ },
507
+ style: {
508
+ transform: `rotate(${turns.right}deg)`,
509
+ opacity: turns.rightShown
510
+ },
511
+ children: halfRing
512
+ })
513
+ }), /*#__PURE__*/_jsx("span", {
514
+ ...{
515
+ className: "x10l6tqk x13vifvy x3hqpx7 x5yr21d xb3r6kr xu96u03"
516
+ },
517
+ children: /*#__PURE__*/_jsx("span", {
518
+ ref: leftRef,
519
+ ...{
520
+ className: "x10l6tqk x13vifvy x75ztot x5yr21d x1q1rmc8 xu96u03"
521
+ },
522
+ style: {
523
+ transform: `rotate(${turns.left}deg)`,
524
+ opacity: turns.leftShown
525
+ },
526
+ children: halfRing
527
+ })
528
+ }), /*#__PURE__*/_jsx("svg", {
529
+ width: diameter,
530
+ height: diameter,
531
+ viewBox: `0 0 ${diameter} ${diameter}`,
532
+ ...{
533
+ className: "x10l6tqk x10a8y8t x1q1rmc8 x1lliihq xbh8q5q x1owpc8m"
534
+ },
535
+ children: /*#__PURE__*/_jsx("path", {
536
+ ref: startRef,
537
+ d: capPath,
538
+ strokeWidth: strokeWidth,
539
+ style: {
540
+ opacity: turns.caps
541
+ }
542
+ })
543
+ }), /*#__PURE__*/_jsx("span", {
544
+ ref: capRef,
545
+ ...{
546
+ className: "x10l6tqk x10a8y8t x1q1rmc8"
547
+ },
548
+ style: {
549
+ transform: `rotate(${turns.cap}deg)`,
550
+ opacity: turns.caps
551
+ },
552
+ children: /*#__PURE__*/_jsx("svg", {
553
+ width: diameter + 2 * FILL_BLEED,
554
+ height: diameter + 2 * FILL_BLEED,
555
+ viewBox: bleedBox,
556
+ ...{
557
+ className: "x1owpc8m x1lliihq xbh8q5q xy6n8ys"
558
+ },
559
+ children: /*#__PURE__*/_jsx("path", {
560
+ d: capPath,
561
+ strokeWidth: strokeWidth
562
+ })
563
+ })
287
564
  })]
288
565
  }), centerContent != null && /*#__PURE__*/_jsx("div", {
289
566
  ...{
@@ -1,4 +1,6 @@
1
1
  import type { BaseProps } from '@astryxdesign/core';
2
+ /** Pin heights on the shared element-size scale: 28, 32, or 36px. */
3
+ export type CommentPinSize = 'sm' | 'md' | 'lg';
2
4
  /** Identity data for one representative shown inside a CommentPin. */
3
5
  export interface CommentPinParticipant {
4
6
  /** Stable identity used to remove duplicate representatives. */
@@ -21,10 +23,17 @@ export interface CommentPinProps extends Omit<BaseProps<HTMLButtonElement>, 'chi
21
23
  * Ordered participants, lead first. A single marker shows the first. A
22
24
  * grouped marker shows distinct IDs as an AvatarGroup of at most three
23
25
  * circles: every participant when three or fewer are distinct, otherwise the
24
- * first two plus a `+N` overflow circle. Pass every distinct participant so
25
- * the overflow count is accurate.
26
+ * first two plus an ellipsis overflow circle. Pass every distinct
27
+ * participant so the overflow appears only when more than three people
28
+ * participate.
26
29
  */
27
30
  participants: readonly [CommentPinParticipant, ...CommentPinParticipant[]];
31
+ /**
32
+ * Pin height on the element-size scale: `sm` 28px, `md` 32px, `lg` 36px,
33
+ * with 22px, 24px, and 28px participant circles.
34
+ * @default 'sm'
35
+ */
36
+ size?: CommentPinSize;
28
37
  /**
29
38
  * Total number of threads represented by this marker. Values above one use
30
39
  * the grouped presentation and show the exact total.
@@ -33,8 +42,9 @@ export interface CommentPinProps extends Omit<BaseProps<HTMLButtonElement>, 'chi
33
42
  threadCount?: number;
34
43
  /**
35
44
  * Number of represented threads with unread activity. When above zero, an
36
- * accent badge shows this number in place of the thread total, on single
37
- * and grouped pins alike. Values above `threadCount` are capped.
45
+ * accent status dot appears at the end of the pin: after the thread total,
46
+ * or after the participant on a single pin. Values above `threadCount` are
47
+ * capped.
38
48
  * @default 0
39
49
  */
40
50
  unreadCount?: number;
@@ -45,7 +55,11 @@ export interface CommentPinProps extends Omit<BaseProps<HTMLButtonElement>, 'chi
45
55
  * disabling it, so it remains available to switch selection.
46
56
  */
47
57
  isDimmed?: boolean;
48
- /** Whether every thread represented by this pin is resolved. */
58
+ /**
59
+ * Whether every thread represented by this pin is resolved. A resolved pin
60
+ * renders nothing unless it is selected, so resolved discussion leaves the
61
+ * content while a thread the reader opened keeps its anchor.
62
+ */
49
63
  isResolved?: boolean;
50
64
  /** Whether activation is unavailable. */
51
65
  isDisabled?: boolean;
@@ -56,7 +70,8 @@ export interface CommentPinProps extends Omit<BaseProps<HTMLButtonElement>, 'chi
56
70
  * The caller owns grouping, representative selection, coordinates, collision
57
71
  * handling, and the content opened by activation. Absolute placement maps the
58
72
  * host anchor to the pin's logical bottom-start corner. The pin only owns its
59
- * compact button surface and controlled visual states.
73
+ * compact button surface and controlled visual states. `size` sets its height
74
+ * on the element-size scale.
60
75
  *
61
76
  * @example
62
77
  * ```
@@ -79,7 +94,7 @@ export interface CommentPinProps extends Omit<BaseProps<HTMLButtonElement>, 'chi
79
94
  * />
80
95
  * ```
81
96
  */
82
- export declare function CommentPin({ label, participants, threadCount, unreadCount, isSelected, isDimmed, isResolved, isDisabled, ref, xstyle, className, style, ...rest }: CommentPinProps): React.ReactNode;
97
+ export declare function CommentPin({ label, participants, size, threadCount, unreadCount, isSelected, isDimmed, isResolved, isDisabled, ref, xstyle, className, style, ...rest }: CommentPinProps): React.ReactNode;
83
98
  export declare namespace CommentPin {
84
99
  var displayName: string;
85
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CommentPin.d.ts","sourceRoot":"","sources":["../../src/CommentPin/CommentPin.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA2BlD,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,iBAAiB,CAAC,EAC5B,UAAU,CACX;IACC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,YAAY,EAAE,SAAS,CAAC,qBAAqB,EAAE,GAAG,qBAAqB,EAAE,CAAC,CAAC;IAC3E;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAqMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,YAAY,EACZ,WAAe,EACf,WAAe,EACf,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAClB,UAAkB,EAClB,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,eAAe,GAAG,KAAK,CAAC,SAAS,CAyInC;yBAvJe,UAAU"}
1
+ {"version":3,"file":"CommentPin.d.ts","sourceRoot":"","sources":["../../src/CommentPin/CommentPin.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA4BlD,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,iBAAiB,CAAC,EAC5B,UAAU,CACX;IACC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,CAAC,qBAAqB,EAAE,GAAG,qBAAqB,EAAE,CAAC,CAAC;IAC3E;;;;OAIG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA8ND;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,YAAY,EACZ,IAAW,EACX,WAAe,EACf,WAAe,EACf,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAClB,UAAkB,EAClB,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,eAAe,GAAG,KAAK,CAAC,SAAS,CA4JnC;yBA3Ke,UAAU"}