eidotter 0.41.1 → 0.41.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.
- package/README.md +13 -1
- package/dist/components/CmdPalette/components/CmdPalette.d.ts +6 -1
- package/dist/components/DosDialog/components/DosDialog.d.ts +13 -2
- package/dist/components/DosDialog/components/DosDialog.js +4 -2
- package/dist/components/DosDialog/components/index.d.ts +1 -1
- package/dist/components/DosDialog/index.d.ts +1 -1
- package/dist/components/Header/components/Header.d.ts +1 -1
- package/dist/components/Header/components/Header.js +4 -1
- package/dist/components/Terminal/components/Terminal.js +1 -1
- package/dist/components/registry.js +14 -8
- package/dist/eidotter.css +1 -1
- package/dist/hooks/useTextScramble.js +27 -31
- package/dist/index.d.ts +2 -2
- package/dist/index.es.js +352 -314
- package/dist/index.umd.js +1 -1
- package/llms.txt +14 -1
- package/migrations/0.41.0.md +112 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,6 +79,18 @@ module.exports = {
|
|
|
79
79
|
</div>
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
### With Tailwind v4: load eiDotter's CSS in a cascade layer
|
|
83
|
+
|
|
84
|
+
`dist/eidotter.css` ships the stock utilities its components use (`.flex`, `.flex-col`, `.hidden`, about 200) unlayered, and unlayered CSS beats Tailwind v4's `@layer utilities` whatever the order: your `md:flex-row` loses to eiDotter's `.flex-col`. Import eiDotter into a named layer before your utilities, in the CSS file that imports Tailwind:
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
@layer theme, base, components, eidotter, utilities;
|
|
88
|
+
@import "tailwindcss";
|
|
89
|
+
@import "eidotter/styles" layer(eidotter);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Drop the JS `import 'eidotter/styles'` (it would add an unlayered second copy) and do not point `@source` at `node_modules/eidotter/dist`. Trade-off: inside the layer, eiDotter loses to any unlayered global rule of yours (an unlayered `* { margin: 0; padding: 0 }` beats eiDotter's component utilities), and eiDotter's reset beats element defaults in your `@layer base`. Keep your resets layered, and put global element styles in a layer declared after `eidotter`. Details: [migrations/0.41.0.md](./migrations/0.41.0.md#tailwind-v4-apps-load-eidotters-css-in-a-cascade-layer-recommended).
|
|
93
|
+
|
|
82
94
|
The preset auto-registers `tailwindcss-react-aria-components` and `tailwindcss-animate` if they resolve. Migrating from the pre-0.19.2 dual-preset split? See [CHANGELOG → 0.19.2](./CHANGELOG.md#0192--2026-04-18).
|
|
83
95
|
|
|
84
96
|
### Server Components (RSC / Next.js App Router)
|
|
@@ -104,7 +116,7 @@ for client apps and `'use client'` files.
|
|
|
104
116
|
|
|
105
117
|
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
118
|
|
|
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.
|
|
119
|
+
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.3` (0.41.0 shipped before its review fixes; use the latest 0.41.x, 0.41.3 or later).
|
|
108
120
|
|
|
109
121
|
## Documentation
|
|
110
122
|
|
|
@@ -33,7 +33,12 @@ export interface CmdPaletteProps {
|
|
|
33
33
|
footerHint?: React.ReactNode;
|
|
34
34
|
/** Custom renderer for each row. Falls back to label + hint. */
|
|
35
35
|
renderItem?: (item: CmdPaletteItem, isSelected: boolean) => React.ReactNode;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Cap on result rows, default `20`. It also applies to the empty query: with
|
|
38
|
+
* no search text the palette lists only the first `maxResults` items, so a
|
|
39
|
+
* site with more than 20 routes hides the rest until the user types. Pass a
|
|
40
|
+
* larger value (e.g. `items.length`) to list every item up front.
|
|
41
|
+
*/
|
|
37
42
|
maxResults?: number;
|
|
38
43
|
/** Extra class names merged onto the dialog container. */
|
|
39
44
|
className?: string;
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export type DosDialogPlacement = 'center' | 'top';
|
|
3
3
|
export type DosDialogSize = 'sm' | 'md' | 'lg' | 'full';
|
|
4
|
+
/** Heading level for a structured, non-decorative head title. */
|
|
5
|
+
export type DosDialogHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
4
6
|
/** Structured head for the title strip. */
|
|
5
7
|
export interface DosDialogHead {
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Title text. Rendered as the dialog's heading (`<h2>` by default, see
|
|
10
|
+
* `headingLevel`) unless `decorative` is set.
|
|
11
|
+
*/
|
|
7
12
|
title: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Heading level of the title, `2` (default) to `6`. Use it when the dialog
|
|
15
|
+
* sits under an existing heading, e.g. an `inline` preview inside a card
|
|
16
|
+
* whose own title is an `<h3>` (pass `4`). Ignored when `decorative` is set.
|
|
17
|
+
*/
|
|
18
|
+
headingLevel?: DosDialogHeadingLevel;
|
|
8
19
|
/** Trailing slot on the right of the strip: close button, status, controls. */
|
|
9
20
|
end?: React.ReactNode;
|
|
10
21
|
/** Show the leading `▸` marker. Defaults to `true`. */
|
|
11
22
|
marker?: boolean;
|
|
12
23
|
/**
|
|
13
24
|
* Visual-only strip: hidden from assistive tech, and the title renders as a
|
|
14
|
-
* `<span>` instead of
|
|
25
|
+
* `<span>` instead of a heading. Use when the dialog is named by `aria-label`
|
|
15
26
|
* and the strip repeats nothing a screen reader needs (CmdPalette's
|
|
16
27
|
* `JUMP TO` banner). Do not put focusable controls in `end` when this is set.
|
|
17
28
|
*/
|
|
@@ -4,6 +4,7 @@ import { forwardRef, isValidElement, useEffect, useId } from 'react';
|
|
|
4
4
|
import { ModalOverlay as AriaModalOverlay, Modal as AriaModal, Dialog as AriaDialog, } from 'react-aria-components';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
import { prefersReducedMotion } from '../../../utils/prefersReducedMotion.js';
|
|
7
|
+
const HEADING_LEVELS = [2, 3, 4, 5, 6];
|
|
7
8
|
function isHeadObject(head) {
|
|
8
9
|
return (typeof head === 'object' &&
|
|
9
10
|
head !== null &&
|
|
@@ -50,8 +51,9 @@ export const DosDialog = forwardRef(({ open, onOpenChange, inline = false, place
|
|
|
50
51
|
}, [unnamed]);
|
|
51
52
|
let strip = null;
|
|
52
53
|
if (structured) {
|
|
53
|
-
const { title, end, marker = true, decorative = false } = structured;
|
|
54
|
-
const
|
|
54
|
+
const { title, end, marker = true, decorative = false, headingLevel = 2 } = structured;
|
|
55
|
+
const level = HEADING_LEVELS.includes(headingLevel) ? headingLevel : 2;
|
|
56
|
+
const TitleTag = decorative ? 'span' : `h${level}`;
|
|
55
57
|
strip = (_jsxs("div", { className: "eidotter-dialog__head", "aria-hidden": decorative || undefined, children: [_jsxs("div", { className: "eidotter-dialog__lead", children: [marker && (_jsx("span", { className: "eidotter-dialog__marker", "aria-hidden": "true", children: "\u25B8" })), _jsx(TitleTag, { id: titleId, className: "eidotter-dialog__title", children: title })] }), end != null && end !== false && (_jsx("div", { className: "eidotter-dialog__end", children: end }))] }));
|
|
56
58
|
}
|
|
57
59
|
else if (head != null && head !== false) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { DosDialog } from './DosDialog.js';
|
|
2
|
-
export type { DosDialogProps, DosDialogHead, DosDialogPlacement, DosDialogSize, } from './DosDialog.js';
|
|
2
|
+
export type { DosDialogProps, DosDialogHead, DosDialogPlacement, DosDialogSize, DosDialogHeadingLevel, } from './DosDialog.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { DosDialog } from './components/index.js';
|
|
2
|
-
export type { DosDialogProps, DosDialogHead, DosDialogPlacement, DosDialogSize, } from './components/index.js';
|
|
2
|
+
export type { DosDialogProps, DosDialogHead, DosDialogPlacement, DosDialogSize, DosDialogHeadingLevel, } from './components/index.js';
|
|
@@ -26,7 +26,7 @@ export interface HeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
26
26
|
brandName?: string;
|
|
27
27
|
/** Href for the brand link (default: "/") */
|
|
28
28
|
brandHref?: string;
|
|
29
|
-
/** Navigation items passed through to Nav */
|
|
29
|
+
/** Navigation items passed through to Nav. An empty array renders no nav and no MENU trigger. */
|
|
30
30
|
items: NavItem[];
|
|
31
31
|
/** Currently active href (highlights matching nav link) */
|
|
32
32
|
activeHref?: string;
|
|
@@ -28,9 +28,12 @@ export const Header = forwardRef(({ brandName, brandHref = '/', items, activeHre
|
|
|
28
28
|
// mobile. The right-hand group wrapper only exists when `end` is passed, so
|
|
29
29
|
// headers without it keep their previous DOM.
|
|
30
30
|
const hasEnd = end != null && end !== false && end !== '';
|
|
31
|
+
// No items → no nav at all: no empty <nav> landmark on desktop and no MENU
|
|
32
|
+
// trigger opening an empty panel on mobile (DMNC-1611).
|
|
33
|
+
const hasItems = Array.isArray(items) && items.length > 0;
|
|
31
34
|
const ActionsGroup = hasEnd ? 'div' : React.Fragment;
|
|
32
35
|
const actionsProps = hasEnd ? { className: 'eidotter-header__actions flex items-center gap-4' } : {};
|
|
33
|
-
const main = (_jsxs(_Fragment, { children: [_jsx(LinkTag, { href: safeBrandHref, className: cn('no-underline text-dos-text-base font-bold tracking-wide', 'eidotter-header__branding', iconBrand && 'eidotter-header__branding--icon inline-flex items-center gap-3'), children: brandContent }), _jsxs(ActionsGroup, { ...actionsProps, children: [_jsx(DesktopNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent }), hasEnd && _jsx("div", { className: "eidotter-header__end flex items-center", children: end }), _jsx(MobileNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent })] })] }));
|
|
36
|
+
const main = (_jsxs(_Fragment, { children: [_jsx(LinkTag, { href: safeBrandHref, className: cn('no-underline text-dos-text-base font-bold tracking-wide', 'eidotter-header__branding', iconBrand && 'eidotter-header__branding--icon inline-flex items-center gap-3'), children: brandContent }), _jsxs(ActionsGroup, { ...actionsProps, children: [hasItems && (_jsx(DesktopNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent })), hasEnd && _jsx("div", { className: "eidotter-header__end flex items-center", children: end }), hasItems && (_jsx(MobileNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent }))] })] }));
|
|
34
37
|
const skipLink = safeSkipTarget ? (_jsx("a", { href: safeSkipTarget, className: "eidotter-header__skip", children: "Skip to content" })) : null;
|
|
35
38
|
return (_jsxs("header", { ref: ref, className: cn(hasContext
|
|
36
39
|
? 'flex flex-col gap-2 px-4 py-3 font-dos'
|
|
@@ -39,5 +39,5 @@ export const Terminal = ({ size = 'md', title = 'MS-DOS Terminal', state = 'acti
|
|
|
39
39
|
if (windowState === 'minimized') {
|
|
40
40
|
return (_jsx(AriaButton, { className: `terminal terminal--minimized ${className}`.trim(), onPress: handleFocus, "aria-label": `Restore ${title} window`, children: _jsxs("div", { className: "terminal__taskbar-item", children: [_jsx(Icon, { name: "App", size: "S" }), _jsx("span", { className: "terminal__taskbar-title", children: title })] }) }));
|
|
41
41
|
}
|
|
42
|
-
return (_jsxs("div", { ref: terminalRef, className: `terminal terminal--${normalizedSize} terminal--${windowState} ${isMaximized ? 'terminal--maximized' : ''} ${className}`.trim(), tabIndex: 0, role: "
|
|
42
|
+
return (_jsxs("div", { ref: terminalRef, className: `terminal terminal--${normalizedSize} terminal--${windowState} ${isMaximized ? 'terminal--maximized' : ''} ${className}`.trim(), tabIndex: 0, role: "group", "aria-label": `${title} terminal window`, onFocus: handleFocus, onKeyDown: handleKeyDown, children: [_jsxs("div", { className: "terminal__title-bar", children: [_jsxs("div", { className: "terminal__title", children: [_jsx(Icon, { name: "App", size: "S", "aria-hidden": "true" }), _jsx("span", { className: "terminal__title-text", children: title })] }), (minimizable || maximizable || closeable) && (_jsxs("div", { className: "terminal__controls", children: [minimizable && (_jsx(AriaButton, { className: "terminal__control terminal__control--minimize", onPress: handleMinimize, "aria-label": "Minimize window", children: _jsx(Icon, { name: "Cancel", size: "S" }) })), maximizable && (_jsx(AriaButton, { className: "terminal__control terminal__control--maximize", onPress: handleMaximize, "aria-label": isMaximized ? "Restore window" : "Maximize window", children: _jsx(Icon, { name: isMaximized ? "Fullscreen" : "Add", size: "S" }) })), closeable && (_jsx(AriaButton, { className: "terminal__control terminal__control--close", onPress: handleClose, "aria-label": "Close window", children: _jsx(Icon, { name: "Close", size: "S" }) }))] }))] }), _jsx("div", { className: "terminal__content", role: "main", children: children || (_jsx("div", { className: "terminal__default-content", children: _jsxs("div", { className: "terminal__prompt", children: [_jsx("span", { className: "terminal__path", children: "C:\\\\>" }), _jsx("span", { className: "terminal__cursor", "aria-hidden": "true" })] }) })) }), resizable && (_jsx("div", { className: "terminal__resize-handle", "aria-hidden": "true" }))] }));
|
|
43
43
|
};
|
|
@@ -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' },
|
|
@@ -554,6 +555,7 @@ export const componentRegistry = {
|
|
|
554
555
|
react: { path: 'src/components/Terminal', status: 'canonical' },
|
|
555
556
|
},
|
|
556
557
|
changelog: [
|
|
558
|
+
{ version: '0.41.3', type: 'changed', description: 'Window root is role=group (named "<title> terminal window"), not role=dialog: it is in-page chrome with no focus trap or aria-modal (DMNC-1611)' },
|
|
557
559
|
{ version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes for window controls' },
|
|
558
560
|
{ version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
|
|
559
561
|
{ version: '0.14.0', type: 'changed', description: 'Breaking: minimizable/maximizable/closeable default to false — consumers must opt in' },
|
|
@@ -664,6 +666,7 @@ export const componentRegistry = {
|
|
|
664
666
|
react: { path: 'src/components/TextScramble', status: 'canonical' },
|
|
665
667
|
},
|
|
666
668
|
changelog: [
|
|
669
|
+
{ 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
670
|
{ version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; adopt cn() and forwardRef' },
|
|
668
671
|
{ version: '0.10.0', type: 'added', description: 'Initial TextScramble — children string, speed, characters, delay; rAF-based reveal' },
|
|
669
672
|
],
|
|
@@ -685,6 +688,8 @@ export const componentRegistry = {
|
|
|
685
688
|
'context:returnTo': { description: 'Breadcrumb return pill — same-tab via linkComponent by default; reuseTab navigates via window.open(href, "rizomorf-shell") named-tab reuse', since: '0.39.0' },
|
|
686
689
|
},
|
|
687
690
|
changelog: [
|
|
691
|
+
{ version: '0.41.3', type: 'changed', description: 'end slot clips the inline axis only (overflow-x: clip) with a 12px inline bleed, so focus rings and phosphor glows of controls in it are no longer cut; overflow: hidden fallback without clip support (DMNC-1611)' },
|
|
692
|
+
{ version: '0.41.3', type: 'changed', description: 'items={[]} renders no nav: no empty <nav> and no MENU trigger below 768px; the brand link gets the house focus ring (2px border-focus outline, 2px offset) in both variants (DMNC-1611)' },
|
|
688
693
|
{ version: '0.41.0', type: 'changed', description: 'Mobile Nav panel close is the shared IconButton (md 36x36, outline, hover invert) (DMNC-1592)' },
|
|
689
694
|
{ version: '0.41.0', type: 'changed', description: 'One portfolio header (DMNC-1584): retro variant now has a 1px dim-amber bottom line with a soft glow under it, brand-amber text (branding + returnTo pill, hover to bright brand amber, no text-shadow), uppercase returnTo pill; brandName without children renders the lockup by default' },
|
|
690
695
|
{ version: '0.41.0', type: 'added', description: '`brand` prop (lockup | mark | text, default lockup), `end` trailing slot (rendered once; after the desktop nav / before MENU on mobile), `skipLinkTarget` skip link (WCAG 2.4.1, off by default) (DMNC-1584)' },
|
|
@@ -747,12 +752,13 @@ export const componentRegistry = {
|
|
|
747
752
|
'size:lg': { description: '40rem (CmdPalette historical 640px)', since: '0.41.0' },
|
|
748
753
|
'size:full': { description: 'Viewport width minus 2x spacing-4 (Lightbox)', since: '0.41.0' },
|
|
749
754
|
'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.
|
|
755
|
+
'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
756
|
},
|
|
752
757
|
platforms: { react: { path: 'src/components/DosDialog', status: 'canonical' } },
|
|
753
758
|
changelog: [
|
|
754
|
-
{ version: '0.
|
|
755
|
-
{ version: '0.
|
|
759
|
+
{ version: '0.41.3', type: 'added', description: 'head.headingLevel (2-6, default 2) sets the title heading level in modal and inline mode, e.g. an inline preview under a card h3; ignored on a decorative head (DMNC-1611)' },
|
|
760
|
+
{ 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)' },
|
|
761
|
+
{ 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
762
|
{ 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
763
|
],
|
|
758
764
|
},
|
|
@@ -783,7 +789,7 @@ export const componentRegistry = {
|
|
|
783
789
|
},
|
|
784
790
|
platforms: { react: { path: 'src/components/DosWindow', status: 'canonical' } },
|
|
785
791
|
changelog: [
|
|
786
|
-
{ version: '0.
|
|
792
|
+
{ 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
793
|
{ 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
794
|
],
|
|
789
795
|
},
|
|
@@ -1018,7 +1024,7 @@ export const componentRegistry = {
|
|
|
1018
1024
|
},
|
|
1019
1025
|
changelog: [
|
|
1020
1026
|
{ version: '0.40.0', type: 'added', description: 'Initial RangeBar (health metabolism family)' },
|
|
1021
|
-
{ version: '0.
|
|
1027
|
+
{ 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
1028
|
],
|
|
1023
1029
|
},
|
|
1024
1030
|
SeriesBars: {
|
|
@@ -1043,8 +1049,8 @@ export const componentRegistry = {
|
|
|
1043
1049
|
changelog: [
|
|
1044
1050
|
{ version: '0.40.0', type: 'added', description: 'Initial SeriesBars (health metabolism family)' },
|
|
1045
1051
|
{ 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.
|
|
1052
|
+
{ 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)' },
|
|
1053
|
+
{ 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
1054
|
],
|
|
1049
1055
|
},
|
|
1050
1056
|
DecayStrip: {
|