eidotter 0.41.1 → 0.41.2
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.
- package/README.md +1 -1
- package/dist/components/registry.js +10 -8
- package/dist/hooks/useTextScramble.js +27 -31
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +237 -226
- package/dist/index.umd.js +1 -1
- package/llms.txt +1 -1
- package/migrations/0.41.0.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ for client apps and `'use client'` files.
|
|
|
104
104
|
|
|
105
105
|
Every minor with breaking or visible changes ships an upgrade guide in the package, at `node_modules/eidotter/migrations/<version>.md` (source: [`migrations/`](./migrations/)). Read every guide between your installed version and the target, oldest first. Start with [0.41.0](./migrations/0.41.0.md).
|
|
106
106
|
|
|
107
|
-
0.x caret ranges stop at the next minor (`^0.40.2` never resolves to 0.41.0), so bump the range explicitly: `npm install eidotter@^0.41.
|
|
107
|
+
0.x caret ranges stop at the next minor (`^0.40.2` never resolves to 0.41.0), so bump the range explicitly: `npm install eidotter@^0.41.2` (0.41.0 shipped before its review fixes; use 0.41.1 or later).
|
|
108
108
|
|
|
109
109
|
## Documentation
|
|
110
110
|
|
|
@@ -429,7 +429,8 @@ export const componentRegistry = {
|
|
|
429
429
|
swiftui: { status: 'planned' },
|
|
430
430
|
},
|
|
431
431
|
changelog: [
|
|
432
|
-
{ version: '0.
|
|
432
|
+
{ version: '0.41.1', type: 'changed', description: 'The unit can drop under the value in a narrow card (a space before the unit is the break opportunity; was a 4px margin with no break) (DMNC-1610)' },
|
|
433
|
+
{ version: '0.41.2', type: 'changed', description: 'scramble: server HTML and first client render show the final value; scramble starts after mount (no hydration mismatch, DMNC-1609)' },
|
|
433
434
|
{ version: '0.40.0', type: 'changed', description: 'Add optional unit, note and target slots (additive; existing consumers unchanged)' },
|
|
434
435
|
{ version: '0.19.0', type: 'changed', description: 'Replace hardcoded font-size pixels with V.37 dos tokens' },
|
|
435
436
|
{ version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2); deprecate size aliases; add size:sm/lg' },
|
|
@@ -664,6 +665,7 @@ export const componentRegistry = {
|
|
|
664
665
|
react: { path: 'src/components/TextScramble', status: 'canonical' },
|
|
665
666
|
},
|
|
666
667
|
changelog: [
|
|
668
|
+
{ version: '0.41.2', type: 'changed', description: 'SSR-safe: server HTML and first client render show the final text; scramble and reduced-motion check run after mount (no hydration mismatch, DMNC-1609)' },
|
|
667
669
|
{ version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; adopt cn() and forwardRef' },
|
|
668
670
|
{ version: '0.10.0', type: 'added', description: 'Initial TextScramble — children string, speed, characters, delay; rAF-based reveal' },
|
|
669
671
|
],
|
|
@@ -747,12 +749,12 @@ export const componentRegistry = {
|
|
|
747
749
|
'size:lg': { description: '40rem (CmdPalette historical 640px)', since: '0.41.0' },
|
|
748
750
|
'size:full': { description: 'Viewport width minus 2x spacing-4 (Lightbox)', since: '0.41.0' },
|
|
749
751
|
'inline:false': { description: 'Modal overlay: React Aria ModalOverlay, portal, scrim, focus trap (default)', since: '0.41.0' },
|
|
750
|
-
'inline:true': { description: 'Same chrome in document flow: no overlay, portal or focus trap; role=group, no aria-modal (docs, static previews)', since: '0.
|
|
752
|
+
'inline:true': { description: 'Same chrome in document flow: no overlay, portal or focus trap; role=group, no aria-modal (docs, static previews)', since: '0.41.1' },
|
|
751
753
|
},
|
|
752
754
|
platforms: { react: { path: 'src/components/DosDialog', status: 'canonical' } },
|
|
753
755
|
changelog: [
|
|
754
|
-
{ version: '0.
|
|
755
|
-
{ version: '0.
|
|
756
|
+
{ version: '0.41.1', type: 'added', description: 'inline prop: renders the same frame, strip, head, body, footer and end control in place without ModalOverlay, portal or focus trap, as a role=group with no aria-modal; server HTML carries the chrome (DMNC-1605)' },
|
|
757
|
+
{ version: '0.41.1', type: 'changed', description: 'Last end IconButton docks flush in the frame corner (0px top, 0px right, was 0px top, 4px right), the same rule as DosWindow controls (DMNC-1605)' },
|
|
756
758
|
{ version: '0.41.0', type: 'added', description: 'Initial DosDialog: open/onOpenChange, placement, size, head strip (title/end/marker/decorative or ReactNode), footer, isDismissable, pop-in/out panel (150ms) over a fading scrim, reduced-motion bypass' },
|
|
757
759
|
],
|
|
758
760
|
},
|
|
@@ -783,7 +785,7 @@ export const componentRegistry = {
|
|
|
783
785
|
},
|
|
784
786
|
platforms: { react: { path: 'src/components/DosWindow', status: 'canonical' } },
|
|
785
787
|
changelog: [
|
|
786
|
-
{ version: '0.
|
|
788
|
+
{ version: '0.41.1', type: 'changed', description: 'Last control IconButton docks flush in the frame corner (0px top, 0px right, was 0px top, 4px right); the inline-end overhang moved from the controls container to the last IconButton, so non-IconButton controls keep the strip padding (DMNC-1605)' },
|
|
787
789
|
{ version: '0.41.0', type: 'added', description: 'Initial DosWindow: title, marker, active, controls, footer, className; stable classes eidotter-window, __strip, __title, __controls, __body, __footer (DMNC-1592)' },
|
|
788
790
|
],
|
|
789
791
|
},
|
|
@@ -1018,7 +1020,7 @@ export const componentRegistry = {
|
|
|
1018
1020
|
},
|
|
1019
1021
|
changelog: [
|
|
1020
1022
|
{ version: '0.40.0', type: 'added', description: 'Initial RangeBar (health metabolism family)' },
|
|
1021
|
-
{ version: '0.
|
|
1023
|
+
{ version: '0.41.1', type: 'changed', description: 'Vertical legend wraps under the bar in a narrow container; legend names wrap instead of truncating and a bound moves to the next line whole; percent and target label separated by a gap and a space; lane and legend text print black on white (DMNC-1610)' },
|
|
1022
1024
|
],
|
|
1023
1025
|
},
|
|
1024
1026
|
SeriesBars: {
|
|
@@ -1043,8 +1045,8 @@ export const componentRegistry = {
|
|
|
1043
1045
|
changelog: [
|
|
1044
1046
|
{ version: '0.40.0', type: 'added', description: 'Initial SeriesBars (health metabolism family)' },
|
|
1045
1047
|
{ version: '0.41.0', type: 'changed', description: 'Goal label moved into a reserved band above the plot so it never paints over a bar (DMNC-1573)' },
|
|
1046
|
-
{ version: '0.
|
|
1047
|
-
{ version: '0.
|
|
1048
|
+
{ version: '0.41.1', type: 'added', description: 'Additive unit and format (formatValue digits/percent/signed) props: values and the goal carry the unit in the summary, value table and bar titles; a goal label that already holds the goal value is not repeated (DMNC-1610)' },
|
|
1049
|
+
{ version: '0.41.1', type: 'changed', description: 'Sparse ticks (tickEvery > 1) overflow into empty neighbour columns instead of truncating; first and last labels pinned to the plot edges; goal tag and ticks print black on white (DMNC-1610)' },
|
|
1048
1050
|
],
|
|
1049
1051
|
},
|
|
1050
1052
|
DecayStrip: {
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
2
|
import { prefersReducedMotion } from '../utils/prefersReducedMotion.js';
|
|
3
3
|
const DEFAULT_CHARACTERS = '░▒▓█│┤┐└┴┬├─┼';
|
|
4
4
|
const CYCLES_PER_CHAR = 4;
|
|
5
|
+
// Layout effect on the client so a client-only mount swaps to the scrambled
|
|
6
|
+
// frame before paint (no flash of the final text); plain effect on the server,
|
|
7
|
+
// where neither runs. Either way the effect runs only after mount/hydration.
|
|
8
|
+
const useClientLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
9
|
+
function scrambleFrame(targetText, characters, resolvedCount) {
|
|
10
|
+
let result = '';
|
|
11
|
+
for (let i = 0; i < targetText.length; i++) {
|
|
12
|
+
if (i < resolvedCount || targetText[i] === ' ') {
|
|
13
|
+
result += targetText[i];
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
result += characters[Math.floor(Math.random() * characters.length)];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
5
21
|
/**
|
|
6
22
|
* Hook that animates text with a DOS-style character scramble effect.
|
|
7
23
|
* Characters resolve left-to-right (CRT beam metaphor).
|
|
@@ -11,24 +27,14 @@ const CYCLES_PER_CHAR = 4;
|
|
|
11
27
|
*/
|
|
12
28
|
export function useTextScramble(targetText, options) {
|
|
13
29
|
const { speed = 40, characters = DEFAULT_CHARACTERS, enabled = true, delay = 0, } = options ?? {};
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
for (let i = 0; i < targetText.length; i++) {
|
|
20
|
-
if (targetText[i] === ' ') {
|
|
21
|
-
result += ' ';
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
result += characters[Math.floor(Math.random() * characters.length)];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
});
|
|
30
|
+
// SSR-safe (DMNC-1609): the server render and the first client render both
|
|
31
|
+
// output the final text, byte-identical, so hydration never mismatches and
|
|
32
|
+
// static HTML carries real content. Randomness and the reduced-motion check
|
|
33
|
+
// live in the effect below, which only runs on the client after mount.
|
|
34
|
+
const [display, setDisplay] = useState(targetText);
|
|
29
35
|
const [isScrambling, setIsScrambling] = useState(false);
|
|
30
36
|
const rafRef = useRef(0);
|
|
31
|
-
|
|
37
|
+
useClientLayoutEffect(() => {
|
|
32
38
|
// Skip animation if disabled or reduced motion
|
|
33
39
|
if (!enabled || prefersReducedMotion()) {
|
|
34
40
|
setDisplay(targetText);
|
|
@@ -40,6 +46,9 @@ export function useTextScramble(targetText, options) {
|
|
|
40
46
|
let cycleCount = 0;
|
|
41
47
|
let lastTime = 0;
|
|
42
48
|
let delayRemaining = delay;
|
|
49
|
+
// First scrambled frame, set before paint so the delay period shows
|
|
50
|
+
// glyphs rather than the final text (as the pre-SSR-fix version did).
|
|
51
|
+
setDisplay(scrambleFrame(targetText, characters, 0));
|
|
43
52
|
setIsScrambling(true);
|
|
44
53
|
const animate = (time) => {
|
|
45
54
|
if (!lastTime)
|
|
@@ -63,20 +72,7 @@ export function useTextScramble(targetText, options) {
|
|
|
63
72
|
setIsScrambling(false);
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
|
-
|
|
67
|
-
let result = '';
|
|
68
|
-
for (let i = 0; i < targetText.length; i++) {
|
|
69
|
-
if (i < resolvedCount) {
|
|
70
|
-
result += targetText[i];
|
|
71
|
-
}
|
|
72
|
-
else if (targetText[i] === ' ') {
|
|
73
|
-
result += ' ';
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
result += characters[Math.floor(Math.random() * characters.length)];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
setDisplay(result);
|
|
75
|
+
setDisplay(scrambleFrame(targetText, characters, resolvedCount));
|
|
80
76
|
}
|
|
81
77
|
rafRef.current = requestAnimationFrame(animate);
|
|
82
78
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -132,4 +132,4 @@ export type { SafeHrefOptions } from './utils';
|
|
|
132
132
|
export { ZOOM_LEVELS } from './components/TimelineContainer';
|
|
133
133
|
export { componentRegistry, projects, getComponentMeta, getComponentsByOrigin, getComponentsByConsumer, getVariantsUsedBy, getVariantConsumerMap, } from './components/registry';
|
|
134
134
|
export type { ComponentMeta, ProjectId, ProjectInfo, VariantMeta, PlatformId, PlatformMeta, ChangelogEntry, } from './components/registry';
|
|
135
|
-
export declare const version = "0.41.
|
|
135
|
+
export declare const version = "0.41.2";
|