@adecore/ui 0.54.4 → 0.54.6

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.
@@ -4,10 +4,11 @@ import { I18nextProvider } from 'react-i18next';
4
4
  import { setFormatSource } from './format/locale.js';
5
5
  import { addUiResources } from './locales.js';
6
6
  import { startInputModality } from './modality.js';
7
+ import { startSyncedAnimations } from './synced-animations.js';
7
8
  import { TooltipProvider } from "./Tooltip.js";
8
9
  import { jsx as _jsx } from "react/jsx-runtime";
9
10
  export function UIProvider(t0) {
10
- const $ = _c(7);
11
+ const $ = _c(8);
11
12
  const {
12
13
  i18n,
13
14
  formatSource,
@@ -43,26 +44,34 @@ export function UIProvider(t0) {
43
44
  }
44
45
  useLayoutEffect(startInputModality, t2);
45
46
  let t3;
46
- if ($[2] !== children) {
47
- t3 = /*#__PURE__*/_jsx(TooltipProvider, {
47
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
48
+ t3 = [];
49
+ $[2] = t3;
50
+ } else {
51
+ t3 = $[2];
52
+ }
53
+ useLayoutEffect(startSyncedAnimations, t3);
54
+ let t4;
55
+ if ($[3] !== children) {
56
+ t4 = /*#__PURE__*/_jsx(TooltipProvider, {
48
57
  children: children
49
58
  });
50
- $[2] = children;
51
- $[3] = t3;
59
+ $[3] = children;
60
+ $[4] = t4;
52
61
  } else {
53
- t3 = $[3];
62
+ t4 = $[4];
54
63
  }
55
- let t4;
56
- if ($[4] !== i18n || $[5] !== t3) {
57
- t4 = /*#__PURE__*/_jsx(I18nextProvider, {
64
+ let t5;
65
+ if ($[5] !== i18n || $[6] !== t4) {
66
+ t5 = /*#__PURE__*/_jsx(I18nextProvider, {
58
67
  i18n: i18n,
59
- children: t3
68
+ children: t4
60
69
  });
61
- $[4] = i18n;
62
- $[5] = t3;
70
+ $[5] = i18n;
63
71
  $[6] = t4;
72
+ $[7] = t5;
64
73
  } else {
65
- t4 = $[6];
74
+ t5 = $[7];
66
75
  }
67
- return t4;
76
+ return t5;
68
77
  }
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export { Slider, type SliderProps } from './Slider.tsx';
60
60
  export { SlidingColumn, type SlidingColumnProps } from './SlidingColumn.tsx';
61
61
  export { Spinner, type SpinnerProps } from './Spinner.tsx';
62
62
  export { SplitButton, type SplitButtonProps } from './SplitButton.tsx';
63
+ export { syncAnimations } from './synced-animations.ts';
63
64
  export { Stepper, type StepperProps } from './Stepper.tsx';
64
65
  export { Surface, type SurfaceProps } from './Surface.tsx';
65
66
  export { Switch, type SwitchProps } from './Switch.tsx';
package/dist/index.js CHANGED
@@ -57,6 +57,7 @@ export { Slider } from "./Slider.js";
57
57
  export { SlidingColumn } from "./SlidingColumn.js";
58
58
  export { Spinner } from "./Spinner.js";
59
59
  export { SplitButton } from "./SplitButton.js";
60
+ export { syncAnimations } from './synced-animations.js';
60
61
  export { Stepper } from "./Stepper.js";
61
62
  export { Surface } from "./Surface.js";
62
63
  export { Switch } from "./Switch.js";
@@ -0,0 +1,8 @@
1
+ interface AnimationStart {
2
+ readonly animationName: string;
3
+ readonly target: EventTarget | null;
4
+ }
5
+ export declare function syncAnimations(added: readonly string[]): void;
6
+ export declare function alignAnimation({ animationName, target }: AnimationStart): void;
7
+ export declare function startSyncedAnimations(): void;
8
+ export {};
@@ -0,0 +1,42 @@
1
+ /* `spinner-hop` is the library's; an app adds its own with `syncAnimations`. */
2
+ const names = new Set(['spinner-hop']);
3
+ let started = false;
4
+ const isCssAnimation = animation => 'animationName' in animation;
5
+ /* Adds looping animations by their `@keyframes` name. A one-off animation does not belong here: on the
6
+ document's clock it would start long over. Neither does one that restarts on purpose, such as a caret's blink. */
7
+ export function syncAnimations(added) {
8
+ for (const name of added) {
9
+ names.add(name);
10
+ }
11
+ }
12
+ /* Moves a listed animation that just started onto the document timeline's zero. Its own delay still
13
+ applies, so the dots of a spinner keep their thirds. */
14
+ export function alignAnimation({
15
+ animationName,
16
+ target
17
+ }) {
18
+ if (!names.has(animationName) || target === null || !('getAnimations' in target)) {
19
+ return;
20
+ }
21
+ for (const animation of target.getAnimations()) {
22
+ if (isCssAnimation(animation) && animation.animationName === animationName && animation.startTime !== 0) {
23
+ animation.startTime = 0;
24
+ }
25
+ }
26
+ }
27
+ /* A CSS animation starts when its element is styled, so two spinners mounted a moment apart take their
28
+ steps at other moments, and every step of each is a frame of its own. On one clock the steps of every
29
+ listed animation fall in the same frames, whatever mounted when.
30
+
31
+ On `animationstart` and not on mount, so nothing reads styles in a commit; the first step may show in
32
+ its own phase before it moves onto the grid. `UIProvider` starts it; a second call does nothing. */
33
+ export function startSyncedAnimations() {
34
+ if (started) {
35
+ return;
36
+ }
37
+ started = true;
38
+ document.addEventListener('animationstart', alignAnimation, {
39
+ capture: true,
40
+ passive: true
41
+ });
42
+ }
package/dist/theme.css CHANGED
@@ -630,7 +630,8 @@
630
630
  Each dot turns half a circle over the other two, then slides back one gap at a time; the three
631
631
  are a third of a turn apart, so at every moment one dot is in the air. The hops move in steps:
632
632
  a smooth one changes every frame and keeps the compositor drawing the window at the display's
633
- rate for as long as anything works. The three share their step boundaries, so a step is one frame. */
633
+ rate for as long as anything works. The three share their step boundaries, so a step is one frame,
634
+ and every spinner shares them with the others on the document's clock (`synced-animations.ts`). */
634
635
  .spinner {
635
636
  --spinner-duration: 1800ms;
636
637
  --spinner-dot: round(calc(var(--spinner-size) * 0.22), 1px);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/ui",
3
- "version": "0.54.4",
3
+ "version": "0.54.6",
4
4
  "description": "React components, a theme, formatters and a settings dialog for desktop-grade apps on React 19, Base UI and Tailwind 4.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "author": "Bas Milius",
@@ -4,6 +4,7 @@ import { I18nextProvider } from 'react-i18next';
4
4
  import { setFormatSource, type FormatSource } from './format/locale.ts';
5
5
  import { addUiResources } from './locales.ts';
6
6
  import { startInputModality } from './modality.ts';
7
+ import { startSyncedAnimations } from './synced-animations.ts';
7
8
  import { TooltipProvider } from './Tooltip.tsx';
8
9
 
9
10
  export interface UIProviderProps {
@@ -32,6 +33,7 @@ export function UIProvider({ i18n, formatSource, children }: UIProviderProps) {
32
33
  }
33
34
 
34
35
  useLayoutEffect(startInputModality, []);
36
+ useLayoutEffect(startSyncedAnimations, []);
35
37
 
36
38
  return (
37
39
  <I18nextProvider i18n={i18n}>
package/src/index.ts CHANGED
@@ -78,6 +78,7 @@ export { Slider, type SliderProps } from './Slider.tsx';
78
78
  export { SlidingColumn, type SlidingColumnProps } from './SlidingColumn.tsx';
79
79
  export { Spinner, type SpinnerProps } from './Spinner.tsx';
80
80
  export { SplitButton, type SplitButtonProps } from './SplitButton.tsx';
81
+ export { syncAnimations } from './synced-animations.ts';
81
82
  export { Stepper, type StepperProps } from './Stepper.tsx';
82
83
  export { Surface, type SurfaceProps } from './Surface.tsx';
83
84
  export { Switch, type SwitchProps } from './Switch.tsx';
@@ -0,0 +1,49 @@
1
+ /* `spinner-hop` is the library's; an app adds its own with `syncAnimations`. */
2
+ const names = new Set<string>(['spinner-hop']);
3
+
4
+ let started = false;
5
+
6
+ /* What an `animationstart` hands the listener: the target is an element, which a test fakes without a DOM. */
7
+ interface AnimationStart {
8
+ readonly animationName: string;
9
+ readonly target: EventTarget | null;
10
+ }
11
+
12
+ const isCssAnimation = (animation: Animation): animation is CSSAnimation => 'animationName' in animation;
13
+
14
+ /* Adds looping animations by their `@keyframes` name. A one-off animation does not belong here: on the
15
+ document's clock it would start long over. Neither does one that restarts on purpose, such as a caret's blink. */
16
+ export function syncAnimations(added: readonly string[]): void {
17
+ for (const name of added) {
18
+ names.add(name);
19
+ }
20
+ }
21
+
22
+ /* Moves a listed animation that just started onto the document timeline's zero. Its own delay still
23
+ applies, so the dots of a spinner keep their thirds. */
24
+ export function alignAnimation({ animationName, target }: AnimationStart): void {
25
+ if (!names.has(animationName) || target === null || !('getAnimations' in target)) {
26
+ return;
27
+ }
28
+
29
+ for (const animation of (target as Element).getAnimations()) {
30
+ if (isCssAnimation(animation) && animation.animationName === animationName && animation.startTime !== 0) {
31
+ animation.startTime = 0;
32
+ }
33
+ }
34
+ }
35
+
36
+ /* A CSS animation starts when its element is styled, so two spinners mounted a moment apart take their
37
+ steps at other moments, and every step of each is a frame of its own. On one clock the steps of every
38
+ listed animation fall in the same frames, whatever mounted when.
39
+
40
+ On `animationstart` and not on mount, so nothing reads styles in a commit; the first step may show in
41
+ its own phase before it moves onto the grid. `UIProvider` starts it; a second call does nothing. */
42
+ export function startSyncedAnimations(): void {
43
+ if (started) {
44
+ return;
45
+ }
46
+
47
+ started = true;
48
+ document.addEventListener('animationstart', alignAnimation, { capture: true, passive: true });
49
+ }
package/src/theme.css CHANGED
@@ -630,7 +630,8 @@
630
630
  Each dot turns half a circle over the other two, then slides back one gap at a time; the three
631
631
  are a third of a turn apart, so at every moment one dot is in the air. The hops move in steps:
632
632
  a smooth one changes every frame and keeps the compositor drawing the window at the display's
633
- rate for as long as anything works. The three share their step boundaries, so a step is one frame. */
633
+ rate for as long as anything works. The three share their step boundaries, so a step is one frame,
634
+ and every spinner shares them with the others on the document's clock (`synced-animations.ts`). */
634
635
  .spinner {
635
636
  --spinner-duration: 1800ms;
636
637
  --spinner-dot: round(calc(var(--spinner-size) * 0.22), 1px);