@design-edito/tools 0.5.2 → 0.5.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/agnostic/html/deep-select/index.js +5 -3
  2. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +8 -8
  3. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +8 -8
  4. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +2 -2
  5. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +2 -2
  6. package/agnostic/html/index.d.ts +1 -1
  7. package/agnostic/html/index.js +1 -1
  8. package/agnostic/misc/index.d.ts +3 -3
  9. package/agnostic/misc/index.js +3 -3
  10. package/agnostic/numbers/index.d.ts +1 -1
  11. package/agnostic/numbers/index.js +1 -1
  12. package/agnostic/optim/index.d.ts +1 -1
  13. package/agnostic/optim/index.js +1 -1
  14. package/agnostic/strings/index.d.ts +1 -1
  15. package/agnostic/strings/index.js +1 -1
  16. package/agnostic/time/dates/format-date/index.d.ts +4 -2
  17. package/agnostic/time/dates/format-date/index.js +43 -94
  18. package/agnostic/time/dates/format-date/index.test.js +154 -0
  19. package/agnostic/time/dates/format-date/parts.d.ts +28 -0
  20. package/agnostic/time/dates/format-date/parts.js +136 -0
  21. package/agnostic/time/dates/format-date/types.d.ts +84 -0
  22. package/agnostic/time/dates/format-date/types.js +1 -0
  23. package/agnostic/time/duration/format-duration/index.d.ts +42 -0
  24. package/agnostic/time/duration/format-duration/index.js +94 -0
  25. package/agnostic/time/duration/format-duration/index.test.d.ts +1 -0
  26. package/agnostic/time/duration/format-duration/index.test.js +148 -0
  27. package/agnostic/time/duration/format-duration/parts.d.ts +32 -0
  28. package/agnostic/time/duration/format-duration/parts.js +107 -0
  29. package/agnostic/time/duration/format-duration/types.d.ts +93 -0
  30. package/agnostic/time/duration/format-duration/types.js +1 -0
  31. package/agnostic/time/duration/index.d.ts +6 -1
  32. package/agnostic/time/duration/index.js +2 -2
  33. package/agnostic/time/duration/index.test.d.ts +1 -0
  34. package/agnostic/time/duration/index.test.js +168 -0
  35. package/agnostic/time/index.d.ts +1 -1
  36. package/agnostic/time/index.js +1 -1
  37. package/components/BeforeAfter/index.controlled.d.ts +25 -25
  38. package/components/BeforeAfter/index.controlled.js +47 -53
  39. package/components/BeforeAfter/index.d.ts +20 -23
  40. package/components/BeforeAfter/index.js +48 -40
  41. package/components/Button/index.js +1 -1
  42. package/components/Clippable/index.d.ts +17 -26
  43. package/components/Clippable/index.js +21 -23
  44. package/components/Disclaimer/index.d.ts +29 -22
  45. package/components/Disclaimer/index.js +29 -24
  46. package/components/Drawer/index.d.ts +45 -25
  47. package/components/Drawer/index.js +61 -52
  48. package/components/EventListener/index.d.ts +4 -5
  49. package/components/Gallery/index.d.ts +14 -16
  50. package/components/Gallery/index.js +25 -30
  51. package/components/Iframe/index.js +2 -2
  52. package/components/Image/index.d.ts +5 -12
  53. package/components/Image/index.js +11 -32
  54. package/components/Input/index.d.ts +23 -19
  55. package/components/Input/index.js +30 -20
  56. package/components/JsonEditor/index.d.ts +177 -9
  57. package/components/JsonEditor/index.js +133 -77
  58. package/components/JsonEditor/types.d.ts +11 -0
  59. package/components/JsonEditor/types.js +1 -0
  60. package/components/JsonEditor/utils.d.ts +8 -0
  61. package/components/JsonEditor/utils.js +23 -0
  62. package/components/ListLoader/index.controlled.d.ts +5 -5
  63. package/components/ListLoader/index.controlled.js +4 -4
  64. package/components/ListLoader/index.d.ts +11 -11
  65. package/components/ListLoader/index.js +12 -12
  66. package/components/Overlayer/index.js +4 -4
  67. package/components/Paginator/index.d.ts +7 -12
  68. package/components/Paginator/index.js +7 -11
  69. package/components/ResizeObserver/index.d.ts +14 -3
  70. package/components/ResizeObserver/index.js +49 -24
  71. package/components/Scrllgngn/index.d.ts +17 -13
  72. package/components/Scrllgngn/index.js +25 -84
  73. package/components/Scrllgngn/utils.d.ts +52 -0
  74. package/components/Scrllgngn/utils.js +102 -0
  75. package/components/ScrollListener/index.d.ts +43 -28
  76. package/components/ScrollListener/index.js +59 -79
  77. package/components/ScrollListener/utils.d.ts +37 -16
  78. package/components/ScrollListener/utils.js +141 -28
  79. package/components/Select/index.d.ts +24 -20
  80. package/components/Select/index.js +30 -20
  81. package/components/Sequencer/index.controlled.d.ts +16 -26
  82. package/components/Sequencer/index.controlled.js +15 -12
  83. package/components/Sequencer/index.d.ts +25 -30
  84. package/components/Sequencer/index.js +20 -34
  85. package/components/Subtitles/index.d.ts +29 -5
  86. package/components/Subtitles/index.js +90 -12
  87. package/components/Textarea/index.d.ts +26 -20
  88. package/components/Textarea/index.js +40 -21
  89. package/components/Theatre/index.d.ts +35 -39
  90. package/components/Theatre/index.js +46 -58
  91. package/components/UIModule/index.d.ts +25 -29
  92. package/components/UIModule/index.js +81 -110
  93. package/components/Video/index.controlled.d.ts +51 -51
  94. package/components/Video/index.controlled.js +75 -72
  95. package/components/Video/index.d.ts +12 -14
  96. package/components/Video/index.js +41 -86
  97. package/components/Video/utils.d.ts +35 -3
  98. package/components/Video/utils.js +37 -14
  99. package/components/index.d.ts +1 -1
  100. package/components/index.js +1 -1
  101. package/components/utils/index.d.ts +22 -0
  102. package/components/utils/index.js +31 -0
  103. package/index.d.ts +1 -1
  104. package/index.js +1 -1
  105. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  106. package/node/@aws-s3/storage/file/index.js +1 -1
  107. package/node/@google-cloud/storage/index.d.ts +1 -1
  108. package/node/@google-cloud/storage/index.js +1 -1
  109. package/node/images/index.d.ts +1 -1
  110. package/node/images/index.js +1 -1
  111. package/node/images/transform/operations/index.d.ts +1 -1
  112. package/node/images/transform/operations/index.js +1 -1
  113. package/node/index.d.ts +1 -1
  114. package/node/index.js +1 -1
  115. package/package.json +20 -5
  116. package/TODO.md +0 -269
  117. package/components/BeforeAfter/utils.d.ts +0 -4
  118. package/components/BeforeAfter/utils.js +0 -9
  119. package/components/Input/index.controlled.d.ts +0 -42
  120. package/components/Input/index.controlled.js +0 -54
  121. package/components/Select/index.controlled.d.ts +0 -43
  122. package/components/Select/index.controlled.js +0 -54
  123. package/components/Subtitles/types.d.ts +0 -24
  124. package/components/Subtitles/utils.d.ts +0 -32
  125. package/components/Subtitles/utils.js +0 -107
  126. package/components/Textarea/index.controlled.d.ts +0 -50
  127. package/components/Textarea/index.controlled.js +0 -75
  128. /package/{components/Subtitles/types.js → agnostic/time/dates/format-date/index.test.d.ts} +0 -0
  129. /package/components/ResizeObserver/{style.module.css → styles.module.css} +0 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The units a duration can be broken down into, keyed the way `format-date`
3
+ * tokens are — `'m'` is minutes, `'M'` is months.
4
+ */
5
+ export type DurationUnit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'Y';
6
+ /** Every token `formatDuration` substitutes in a template. */
7
+ export type DurationToken = 'Y' | 'YY' | 'M' | 'MM' | 'w' | 'ww' | 'd' | 'dd' | 'h' | 'hh' | 'm' | 'mm' | 's' | 'ss' | 'ms';
8
+ /** Options for `getDurationParts`. */
9
+ export type GetDurationPartsOptions = {
10
+ /**
11
+ * The units to break the duration down into, in any order. Omitted or empty,
12
+ * a single unit is picked: the largest one the duration reaches.
13
+ */
14
+ units?: DurationUnit[];
15
+ /**
16
+ * Truncates the duration to a whole number of the smallest requested unit, so
17
+ * no part comes out fractional. Defaults to `false`, which leaves the
18
+ * remainder on the smallest unit.
19
+ */
20
+ floorSmallestUnit?: boolean;
21
+ /**
22
+ * Months and years have no fixed length, so they are approximated. `true`
23
+ * (the default) uses 30-day months and 365-day years, matching what
24
+ * `Duration` itself computes. `false` uses the Gregorian mean year of
25
+ * 365.2425 days and a month of exactly a twelfth of it.
26
+ */
27
+ useApproximateMonthAndYear?: boolean;
28
+ };
29
+ /**
30
+ * A duration's parts, keyed by unit token.
31
+ *
32
+ * Every unit is present: those left out of the breakdown are `0`. All parts
33
+ * carry the sign of the input duration.
34
+ */
35
+ export type DurationTokenParts = {
36
+ /** Milliseconds. */
37
+ ms: number;
38
+ /** Seconds. */
39
+ s: number;
40
+ /** Minutes. */
41
+ m: number;
42
+ /** Hours. */
43
+ h: number;
44
+ /** Days. */
45
+ d: number;
46
+ /** Weeks. */
47
+ w: number;
48
+ /** Months. */
49
+ M: number;
50
+ /** Years. */
51
+ Y: number;
52
+ };
53
+ /**
54
+ * The same parts as {@link DurationTokenParts}, under names that read on their
55
+ * own — one per token, same value.
56
+ */
57
+ export type NamedDurationParts = {
58
+ /** {@link DurationTokenParts.ms} */
59
+ milliseconds: number;
60
+ /** {@link DurationTokenParts.s} */
61
+ seconds: number;
62
+ /** {@link DurationTokenParts.m} */
63
+ minutes: number;
64
+ /** {@link DurationTokenParts.h} */
65
+ hours: number;
66
+ /** {@link DurationTokenParts.d} */
67
+ days: number;
68
+ /** {@link DurationTokenParts.w} */
69
+ weeks: number;
70
+ /** {@link DurationTokenParts.M} */
71
+ months: number;
72
+ /** {@link DurationTokenParts.Y} */
73
+ years: number;
74
+ };
75
+ /** Every part of a duration, reachable both by unit token and by name. */
76
+ export type DurationParts = DurationTokenParts & NamedDurationParts;
77
+ /** Options for `formatDuration`. */
78
+ export type FormatDurationOptions = {
79
+ /**
80
+ * Truncates the duration to a whole number of the smallest unit the template
81
+ * uses. Defaults to `true` here, unlike {@link GetDurationPartsOptions} — a
82
+ * template asks to be displayed, and `0:01:59.60000000001` is not a display.
83
+ * Set it to `false` to keep the fraction.
84
+ */
85
+ floorSmallestUnit?: boolean;
86
+ /**
87
+ * Months and years have no fixed length, so they are approximated. `true`
88
+ * (the default) uses 30-day months and 365-day years, matching what
89
+ * `Duration` itself computes. `false` uses the Gregorian mean year of
90
+ * 365.2425 days and a month of exactly a twelfth of it.
91
+ */
92
+ useApproximateMonthAndYear?: boolean;
93
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -4,8 +4,13 @@
4
4
  export type DurationType = 'milliseconds' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months' | 'years';
5
5
  /**
6
6
  * Supported duration types (short form aliases).
7
+ *
8
+ * @remarks
9
+ * `'M'` and `'Y'` are the `format-date` style tokens, and the ones
10
+ * {@link DurationUnit} is keyed on; `'mo'`, `'mth'` and `'y'` predate them and
11
+ * stay accepted. Mind the casing: `'m'` is minutes, `'M'` is months.
7
12
  */
8
- export type DurationTypeShort = 'ms' | 's' | 'sec' | 'min' | 'm' | 'h' | 'd' | 'w' | 'mo' | 'mth' | 'y';
13
+ export type DurationTypeShort = 'ms' | 's' | 'sec' | 'min' | 'm' | 'h' | 'd' | 'w' | 'mo' | 'mth' | 'M' | 'y' | 'Y';
9
14
  /**
10
15
  * Represents a duration in a specific unit and allows conversion to other units.
11
16
  */
@@ -38,11 +38,11 @@ export class Duration {
38
38
  else if (type === 'w' || type === 'weeks') {
39
39
  this.type = 'weeks';
40
40
  }
41
- else if (type === 'mo' || type === 'mth' || type === 'months') {
41
+ else if (type === 'mo' || type === 'mth' || type === 'M' || type === 'months') {
42
42
  this.type = 'months';
43
43
  }
44
44
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
45
- else if (type === 'y' || type === 'years') {
45
+ else if (type === 'y' || type === 'Y' || type === 'years') {
46
46
  this.type = 'years';
47
47
  }
48
48
  else {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Duration, d, days, h, hours, m, milliseconds, min, minutes, months, ms, mth, s, sec, seconds, w, weeks, y, years, yr } from './index.js';
3
+ describe('Duration', () => {
4
+ describe('unit normalisation', () => {
5
+ it('keeps a long form as it is given', () => {
6
+ expect(new Duration(1, 'milliseconds').type).toBe('milliseconds');
7
+ expect(new Duration(1, 'seconds').type).toBe('seconds');
8
+ expect(new Duration(1, 'minutes').type).toBe('minutes');
9
+ expect(new Duration(1, 'hours').type).toBe('hours');
10
+ expect(new Duration(1, 'days').type).toBe('days');
11
+ expect(new Duration(1, 'weeks').type).toBe('weeks');
12
+ expect(new Duration(1, 'months').type).toBe('months');
13
+ expect(new Duration(1, 'years').type).toBe('years');
14
+ });
15
+ it('expands a short form to its long form', () => {
16
+ expect(new Duration(1, 'ms').type).toBe('milliseconds');
17
+ expect(new Duration(1, 's').type).toBe('seconds');
18
+ expect(new Duration(1, 'sec').type).toBe('seconds');
19
+ expect(new Duration(1, 'min').type).toBe('minutes');
20
+ expect(new Duration(1, 'h').type).toBe('hours');
21
+ expect(new Duration(1, 'd').type).toBe('days');
22
+ expect(new Duration(1, 'w').type).toBe('weeks');
23
+ });
24
+ it('tells minutes from months by the case of the token', () => {
25
+ expect(new Duration(1, 'm').type).toBe('minutes');
26
+ expect(new Duration(1, 'M').type).toBe('months');
27
+ });
28
+ it('still accepts the month and year aliases that predate the format-date tokens', () => {
29
+ expect(new Duration(1, 'mo').type).toBe('months');
30
+ expect(new Duration(1, 'mth').type).toBe('months');
31
+ expect(new Duration(1, 'y').type).toBe('years');
32
+ expect(new Duration(1, 'Y').type).toBe('years');
33
+ });
34
+ it('falls back to milliseconds on a unit it does not know', () => {
35
+ // The fallback is unreachable through the types — only a caller in plain JS
36
+ // gets there, and it is that caller the test stands in for.
37
+ const duration = new Duration(5, 'fortnights');
38
+ expect(duration.type).toBe('milliseconds');
39
+ expect(duration.toMs()).toBe(5);
40
+ });
41
+ });
42
+ describe('toMilliseconds', () => {
43
+ it('converts each unit by its fixed length', () => {
44
+ expect(milliseconds(1).toMilliseconds()).toBe(1);
45
+ expect(seconds(1).toMilliseconds()).toBe(1000);
46
+ expect(minutes(1).toMilliseconds()).toBe(60000);
47
+ expect(hours(1).toMilliseconds()).toBe(3600000);
48
+ expect(days(1).toMilliseconds()).toBe(86400000);
49
+ expect(weeks(1).toMilliseconds()).toBe(604800000);
50
+ });
51
+ it('approximates a month as 30 days and a year as 365', () => {
52
+ expect(months(1).toMilliseconds()).toBe(30 * 86400000);
53
+ expect(years(1).toMilliseconds()).toBe(365 * 86400000);
54
+ });
55
+ it('carries the sign and the fraction of the value it was given', () => {
56
+ expect(seconds(-90).toMilliseconds()).toBe(-90000);
57
+ expect(hours(1.5).toMilliseconds()).toBe(5400000);
58
+ });
59
+ });
60
+ describe('conversions to other units', () => {
61
+ it('reads a duration back in a larger unit', () => {
62
+ expect(seconds(90).toMinutes()).toBe(1.5);
63
+ expect(minutes(90).toHours()).toBe(1.5);
64
+ expect(hours(36).toDays()).toBe(1.5);
65
+ expect(days(14).toWeeks()).toBe(2);
66
+ });
67
+ it('reads a duration back in a smaller unit', () => {
68
+ expect(hours(2).toSeconds()).toBe(7200);
69
+ expect(days(1).toMinutes()).toBe(1440);
70
+ });
71
+ it('divides by the same month and year approximations it multiplies by', () => {
72
+ expect(days(30).toMonths()).toBe(1);
73
+ expect(days(365).toYear()).toBe(1);
74
+ });
75
+ it('yields a fractional value rather than a truncated one', () => {
76
+ expect(seconds(30).toMinutes()).toBe(0.5);
77
+ expect(days(400).toYear()).toBeCloseTo(1.0959, 4);
78
+ });
79
+ });
80
+ describe('shorthand aliases', () => {
81
+ it('exposes a shorthand for every conversion', () => {
82
+ const duration = minutes(3);
83
+ expect(duration.toMs()).toBe(duration.toMilliseconds());
84
+ expect(duration.toS()).toBe(duration.toSeconds());
85
+ expect(duration.toSec()).toBe(duration.toSeconds());
86
+ expect(duration.toM()).toBe(duration.toMinutes());
87
+ expect(duration.toMin()).toBe(duration.toMinutes());
88
+ expect(duration.toH()).toBe(duration.toHours());
89
+ expect(duration.toD()).toBe(duration.toDays());
90
+ expect(duration.toW()).toBe(duration.toWeeks());
91
+ expect(duration.toMth()).toBe(duration.toMonths());
92
+ expect(duration.toY()).toBe(duration.toYear());
93
+ expect(duration.toYr()).toBe(duration.toYear());
94
+ });
95
+ it('keeps its conversions bound once they are detached from the instance', () => {
96
+ const { toMs, toMinutes } = hours(2);
97
+ expect(toMs()).toBe(7200000);
98
+ expect(toMinutes()).toBe(120);
99
+ });
100
+ });
101
+ });
102
+ describe('milliseconds', () => {
103
+ it('builds a duration counted in milliseconds', () => {
104
+ expect(milliseconds(250)).toMatchObject({ value: 250, type: 'milliseconds' });
105
+ });
106
+ it('is also exported as ms', () => {
107
+ expect(ms).toBe(milliseconds);
108
+ });
109
+ });
110
+ describe('seconds', () => {
111
+ it('builds a duration counted in seconds', () => {
112
+ expect(seconds(30)).toMatchObject({ value: 30, type: 'seconds' });
113
+ });
114
+ it('is also exported as s and sec', () => {
115
+ expect(s).toBe(seconds);
116
+ expect(sec).toBe(seconds);
117
+ });
118
+ });
119
+ describe('minutes', () => {
120
+ it('builds a duration counted in minutes', () => {
121
+ expect(minutes(45)).toMatchObject({ value: 45, type: 'minutes' });
122
+ });
123
+ it('is also exported as m and min', () => {
124
+ expect(m).toBe(minutes);
125
+ expect(min).toBe(minutes);
126
+ });
127
+ });
128
+ describe('hours', () => {
129
+ it('builds a duration counted in hours', () => {
130
+ expect(hours(12)).toMatchObject({ value: 12, type: 'hours' });
131
+ });
132
+ it('is also exported as h', () => {
133
+ expect(h).toBe(hours);
134
+ });
135
+ });
136
+ describe('days', () => {
137
+ it('builds a duration counted in days', () => {
138
+ expect(days(7)).toMatchObject({ value: 7, type: 'days' });
139
+ });
140
+ it('is also exported as d', () => {
141
+ expect(d).toBe(days);
142
+ });
143
+ });
144
+ describe('weeks', () => {
145
+ it('builds a duration counted in weeks', () => {
146
+ expect(weeks(3)).toMatchObject({ value: 3, type: 'weeks' });
147
+ });
148
+ it('is also exported as w', () => {
149
+ expect(w).toBe(weeks);
150
+ });
151
+ });
152
+ describe('months', () => {
153
+ it('builds a duration counted in months', () => {
154
+ expect(months(2)).toMatchObject({ value: 2, type: 'months' });
155
+ });
156
+ it('is also exported as mth', () => {
157
+ expect(mth).toBe(months);
158
+ });
159
+ });
160
+ describe('years', () => {
161
+ it('builds a duration counted in years', () => {
162
+ expect(years(1)).toMatchObject({ value: 1, type: 'years' });
163
+ });
164
+ it('is also exported as y and yr', () => {
165
+ expect(y).toBe(years);
166
+ expect(yr).toBe(years);
167
+ });
168
+ });
@@ -1,5 +1,5 @@
1
1
  export * as dates from './dates/index.js'
2
- export * as timeout from './timeout/index.js'
3
2
  export * as duration from './duration/index.js'
3
+ export * as timeout from './timeout/index.js'
4
4
  export * as transitions from './transitions/index.js'
5
5
  export * as wait from './wait/index.js'
@@ -1,5 +1,5 @@
1
1
  export * as dates from './dates/index.js'
2
- export * as timeout from './timeout/index.js'
3
2
  export * as duration from './duration/index.js'
3
+ export * as timeout from './timeout/index.js'
4
4
  export * as transitions from './transitions/index.js'
5
5
  export * as wait from './wait/index.js'
@@ -1,45 +1,39 @@
1
- import { type ReactNode, type FunctionComponent, type PropsWithChildren } from 'react';
1
+ import { type FunctionComponent, type PropsWithChildren, type ReactNode } from 'react';
2
2
  import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
- * Props for the {@link BeforeAfterControlled} component.
5
- *
6
- * Extends {@link WithClassName} with layout, content, and interaction configuration.
4
+ * Props for the {@link ControlledBeforeAfter} component.
7
5
  *
8
6
  * @property mode - Layout orientation of the split. Defaults to `'horizontal'`.
9
- * @property ratio - Position of the divider as a value between `0` and `1`.
10
- * Values outside this range are clamped automatically. Defaults to `0`.
7
+ * @property ratio - Position of the divider, between `0` and `1`. Values outside
8
+ * that range are clamped. Defaults to `0`.
11
9
  * @property before - Content rendered in the first (before) panel.
12
10
  * @property after - Content rendered in the second (after) panel.
13
- * @property actionHandlers - Optional user action callbacks:
14
- * - `dragged` called on each pointer move while dragging, with the current
15
- * x and y ratios relative to the component bounds.
16
- * - `clicked` — called on pointer release when no drag occurred, with the
17
- * x and y ratios of the release position.
11
+ * @property onDragged - Called on each pointer move while dragging, with the
12
+ * pointer's x and y ratios relative to the component's bounds.
13
+ * @property onClicked - Called on pointer release when no drag occurred, with
14
+ * the release position's x and y ratios.
18
15
  * @property className - Additional class name(s) applied to the root element.
19
- * @property children - Arbitrary content to inject inside the component
16
+ * @property children - Arbitrary content to inject inside the component.
20
17
  */
21
18
  export type Props = PropsWithChildren<WithClassName<{
22
19
  mode?: 'vertical' | 'horizontal';
23
20
  ratio?: number;
24
21
  before?: ReactNode;
25
22
  after?: ReactNode;
26
- actionHandlers?: {
27
- dragged?: (xRatio: number, yRatio: number) => void;
28
- clicked?: (xRatio: number, yRatio: number) => void;
29
- };
23
+ onDragged?: (xRatio: number, yRatio: number) => void;
24
+ onClicked?: (xRatio: number, yRatio: number) => void;
30
25
  }>>;
31
26
  /**
32
27
  * Controlled before/after comparison component.
33
28
  *
34
- * Renders two content panels separated by a draggable divider whose position
35
- * is expressed as a ratio between `0` and `1`. Supports both mouse and touch
36
- * interactions, distinguishing clicks from drags.
29
+ * Renders two content panels separated by a divider whose position is expressed
30
+ * as a ratio between `0` and `1`. Handles both mouse and touch input, telling a
31
+ * click apart from a drag.
37
32
  *
38
33
  * The active ratio is exposed as:
39
- * - CSS custom properties:
40
- * - `--{prefix}-ratio`
41
- * - `--{prefix}-ratio-percent`
42
- * - A `data-ratio` attribute on the root element.
34
+ * - CSS custom properties `--lm-before-after-ratio` and
35
+ * `--lm-before-after-ratio-percent`,
36
+ * - a `data-ratio` attribute on the root element.
43
37
  *
44
38
  * ### CSS modifiers
45
39
  * - `horizontal` — applied when `mode` is `'horizontal'`.
@@ -48,9 +42,15 @@ export type Props = PropsWithChildren<WithClassName<{
48
42
  * ### CSS elements
49
43
  * - `before`
50
44
  * - `after`
45
+ * - `separator`
51
46
  *
52
47
  * @param props - Component properties.
53
48
  * @see {@link Props}
54
- * @returns A split-panel container with pointer interaction handlers and ratio state applied.
49
+ * @returns A split-panel container reporting pointer interactions as ratios.
50
+ *
51
+ * @remarks
52
+ * The component holds no ratio state: it only reports where the pointer is, and
53
+ * renders whatever `ratio` it is given. Turning one into the other is the
54
+ * uncontrolled wrapper's job.
55
55
  */
56
- export declare const BeforeAfterControlled: FunctionComponent<Props>;
56
+ export declare const ControlledBeforeAfter: FunctionComponent<Props>;
@@ -9,15 +9,14 @@ import cssModule from './styles.module.css';
9
9
  /**
10
10
  * Controlled before/after comparison component.
11
11
  *
12
- * Renders two content panels separated by a draggable divider whose position
13
- * is expressed as a ratio between `0` and `1`. Supports both mouse and touch
14
- * interactions, distinguishing clicks from drags.
12
+ * Renders two content panels separated by a divider whose position is expressed
13
+ * as a ratio between `0` and `1`. Handles both mouse and touch input, telling a
14
+ * click apart from a drag.
15
15
  *
16
16
  * The active ratio is exposed as:
17
- * - CSS custom properties:
18
- * - `--{prefix}-ratio`
19
- * - `--{prefix}-ratio-percent`
20
- * - A `data-ratio` attribute on the root element.
17
+ * - CSS custom properties `--lm-before-after-ratio` and
18
+ * `--lm-before-after-ratio-percent`,
19
+ * - a `data-ratio` attribute on the root element.
21
20
  *
22
21
  * ### CSS modifiers
23
22
  * - `horizontal` — applied when `mode` is `'horizontal'`.
@@ -26,14 +25,18 @@ import cssModule from './styles.module.css';
26
25
  * ### CSS elements
27
26
  * - `before`
28
27
  * - `after`
28
+ * - `separator`
29
29
  *
30
30
  * @param props - Component properties.
31
31
  * @see {@link Props}
32
- * @returns A split-panel container with pointer interaction handlers and ratio state applied.
32
+ * @returns A split-panel container reporting pointer interactions as ratios.
33
+ *
34
+ * @remarks
35
+ * The component holds no ratio state: it only reports where the pointer is, and
36
+ * renders whatever `ratio` it is given. Turning one into the other is the
37
+ * uncontrolled wrapper's job.
33
38
  */
34
- export const BeforeAfterControlled = ({ mode = 'horizontal', ratio = 0, before, after, className, children, actionHandlers }) => {
35
- // eslint-disable-next-line no-param-reassign
36
- ratio = clamp(ratio, 0, 1);
39
+ export const ControlledBeforeAfter = ({ mode = 'horizontal', ratio = 0, before, after, className, children, onDragged, onClicked }) => {
37
40
  // State & refs
38
41
  const rootRef = useRef(null);
39
42
  const isPointerDown = useRef(false);
@@ -48,71 +51,62 @@ export const BeforeAfterControlled = ({ mode = 'horizontal', ratio = 0, before,
48
51
  yRatio: (clientY - rect.top) / rect.height
49
52
  };
50
53
  };
51
- // Handlers
52
- const handleMouseDown = event => {
54
+ const reportDrag = (clientX, clientY) => {
55
+ const ratios = getRatios(clientX, clientY);
56
+ if (ratios !== null)
57
+ onDragged?.(ratios.xRatio, ratios.yRatio);
58
+ };
59
+ const reportClick = (clientX, clientY) => {
60
+ const ratios = getRatios(clientX, clientY);
61
+ if (ratios !== null)
62
+ onClicked?.(ratios.xRatio, ratios.yRatio);
63
+ };
64
+ // User action handlers
65
+ const handlePointerDown = () => {
53
66
  isPointerDown.current = true;
54
67
  hasDragged.current = false;
55
68
  };
69
+ const handlePointerUp = () => {
70
+ isPointerDown.current = false;
71
+ hasDragged.current = false;
72
+ };
56
73
  const handleMouseMove = event => {
57
74
  if (!isPointerDown.current)
58
75
  return;
59
76
  hasDragged.current = true;
60
- const ratios = getRatios(event.clientX, event.clientY);
61
- if (ratios !== null)
62
- actionHandlers?.dragged?.(ratios.xRatio, ratios.yRatio);
77
+ reportDrag(event.clientX, event.clientY);
63
78
  };
64
79
  const handleMouseUp = event => {
65
- if (!hasDragged.current) {
66
- const ratios = getRatios(event.clientX, event.clientY);
67
- if (ratios !== null)
68
- actionHandlers?.clicked?.(ratios.xRatio, ratios.yRatio);
69
- }
70
- isPointerDown.current = false;
71
- hasDragged.current = false;
72
- };
73
- const handleTouchStart = event => {
74
- isPointerDown.current = true;
75
- hasDragged.current = false;
80
+ if (!hasDragged.current)
81
+ reportClick(event.clientX, event.clientY);
82
+ handlePointerUp();
76
83
  };
77
84
  const handleTouchMove = event => {
78
- const t = event.touches[0];
79
- if (t === undefined)
85
+ const touch = event.touches[0];
86
+ if (touch === undefined)
80
87
  return;
81
88
  if (!isPointerDown.current)
82
89
  return;
83
90
  hasDragged.current = true;
84
- const ratios = getRatios(t.clientX, t.clientY);
85
- if (ratios !== null)
86
- actionHandlers?.dragged?.(ratios.xRatio, ratios.yRatio);
91
+ reportDrag(touch.clientX, touch.clientY);
87
92
  };
88
93
  const handleTouchEnd = event => {
89
- if (!hasDragged.current) {
90
- const t = event.changedTouches[0];
91
- if (t !== undefined) {
92
- const ratios = getRatios(t.clientX, t.clientY);
93
- if (ratios !== null)
94
- actionHandlers?.clicked?.(ratios.xRatio, ratios.yRatio);
95
- }
96
- }
97
- isPointerDown.current = false;
98
- hasDragged.current = false;
94
+ const touch = event.changedTouches[0];
95
+ if (!hasDragged.current && touch !== undefined)
96
+ reportClick(touch.clientX, touch.clientY);
97
+ handlePointerUp();
99
98
  };
100
- // Classes & attributes
99
+ // Rendering
100
+ const clampedRatio = clamp(ratio, 0, 1);
101
101
  const c = clss(publicClassName, { cssModule });
102
102
  const rootClss = mergeClassNames(c(null, {
103
103
  horizontal: mode === 'horizontal',
104
104
  vertical: mode === 'vertical'
105
105
  }), className);
106
- const beforeClass = c('before');
107
- const afterClass = c('after');
108
- const separatorClass = c('separator');
106
+ const roundedRatio = round(clampedRatio, 4);
109
107
  const customProps = {
110
- [`--${publicClassName}-ratio`]: `${round(ratio, 4)}`,
111
- [`--${publicClassName}-ratio-percent`]: `${round(ratio * 100, 2)}%`
108
+ '--lm-before-after-ratio': `${roundedRatio}`,
109
+ '--lm-before-after-ratio-percent': `${round(clampedRatio * 100, 2)}%`
112
110
  };
113
- const dataAttributes = {
114
- 'data-ratio': `${round(ratio, 4)}`
115
- };
116
- // Rendering
117
- return _jsxs("div", { ref: rootRef, className: rootClss, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, style: { ...customProps }, ...dataAttributes, children: [_jsx("div", { className: beforeClass, children: before }), _jsx("div", { className: afterClass, children: after }), _jsx("div", { className: separatorClass }), children] });
111
+ return _jsxs("div", { ref: rootRef, className: rootClss, onMouseDown: handlePointerDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onTouchStart: handlePointerDown, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, style: { ...customProps }, "data-ratio": roundedRatio, children: [_jsx("div", { className: c('before'), children: before }), _jsx("div", { className: c('after'), children: after }), _jsx("div", { className: c('separator') }), children] });
118
112
  };
@@ -3,39 +3,36 @@ import { type Props as ControlledProps } from './index.controlled.js';
3
3
  /**
4
4
  * Props for the {@link BeforeAfter} component.
5
5
  *
6
- * Extends {@link ControlledProps} with uncontrolled defaults and state callbacks.
7
- * When `ratio` is provided (inherited from {@link ControlledProps}), the component
8
- * operates in controlled mode and internal state is ignored.
6
+ * Extends {@link ControlledProps} with uncontrolled divider positioning. When
7
+ * `ratio` is provided, the component operates in controlled mode.
9
8
  *
10
- * @property defaultRatio - Initial divider position in uncontrolled mode, as a value
9
+ * @property defaultRatio - Initial divider position in uncontrolled mode,
11
10
  * between `0` and `1`. Ignored when `ratio` is provided. Defaults to `0.5`.
12
- * @property stateHandlers - Optional callbacks invoked when derived state changes:
13
- * - `ratioChanged` — called after the internal ratio has been updated, with the new ratio value.
11
+ * @property onRatioChanged - Called after the divider position changed, with the
12
+ * new ratio.
14
13
  */
15
14
  export type Props = ControlledProps & {
16
15
  defaultRatio?: number;
17
- stateHandlers?: {
18
- ratioChanged?: (ratio: number) => void;
19
- };
16
+ onRatioChanged?: (ratio: number) => void;
20
17
  };
21
18
  /**
22
- * Before/after comparison component with optional controlled and uncontrolled behavior.
19
+ * Before/after comparison component supporting controlled and uncontrolled usage.
23
20
  *
24
- * Wraps {@link BeforeAfterControlled} and manages internal divider position when
25
- * operating in uncontrolled mode. The active axis used to derive the ratio from
26
- * pointer position depends on `mode`: horizontal interactions use the x ratio,
27
- * vertical interactions use the y ratio.
28
- *
29
- * @remarks
30
- * - In controlled mode (`ratio` defined), divider position is fully driven by the prop
31
- * and internal state is never updated.
32
- * - In uncontrolled mode, internal state is initialized from `defaultRatio` and updated
33
- * on both drag and click interactions.
34
- * - `actionHandlers.dragged` and `actionHandlers.clicked` are always forwarded to the
35
- * underlying controlled component, regardless of mode.
21
+ * Turns the pointer ratios reported by {@link ControlledBeforeAfter} into a
22
+ * divider position, reading the axis that matches `mode`: the x ratio when
23
+ * horizontal, the y ratio when vertical.
36
24
  *
37
25
  * @param props - Component properties.
38
26
  * @see {@link Props}
39
- * @returns A {@link BeforeAfterControlled} instance with ratio state managed internally when uncontrolled.
27
+ * @see {@link ControlledBeforeAfter} for the rendered markup and CSS elements.
28
+ * @returns A {@link ControlledBeforeAfter} with the divider position managed
29
+ * internally when uncontrolled.
30
+ *
31
+ * @remarks
32
+ * - In controlled mode (`ratio` defined), the divider position is fully driven
33
+ * by the parent and internal state is never updated.
34
+ * - `onDragged` and `onClicked` fire in both modes — a controlled parent needs
35
+ * them to know where the pointer went.
36
+ * - `onRatioChanged` fires in both modes too, and never on mount.
40
37
  */
41
38
  export declare const BeforeAfter: FunctionComponent<Props>;