eidotter 0.41.2 → 0.42.0
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 +15 -1
- package/dist/components/CmdPalette/components/CmdPalette.d.ts +6 -1
- package/dist/components/CmdPalette/components/CmdPalette.js +5 -3
- 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/IconButton/components/IconButton.d.ts +20 -3
- package/dist/components/IconButton/components/IconButton.js +1 -1
- package/dist/components/IconButton/components/index.d.ts +1 -1
- package/dist/components/IconButton/index.d.ts +1 -1
- package/dist/components/Lightbox/components/Lightbox.d.ts +2 -1
- package/dist/components/Lightbox/components/Lightbox.js +8 -3
- package/dist/components/Nav/components/Nav.d.ts +20 -7
- package/dist/components/Nav/components/index.d.ts +1 -1
- package/dist/components/Nav/index.d.ts +1 -1
- package/dist/components/RangeBar/components/RangeBar.js +2 -2
- package/dist/components/Terminal/components/Terminal.js +1 -1
- package/dist/components/registry.js +13 -0
- package/dist/eidotter.css +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.es.js +915 -1238
- package/dist/index.umd.js +3 -3
- package/guidelines/README.md +37 -0
- package/guidelines/components.md +2 -2
- package/llms.txt +15 -2
- package/migrations/0.41.0.md +114 -22
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -59,6 +59,8 @@ import 'eidotter/utilities'; // Optional: .dos-* typography clas
|
|
|
59
59
|
|
|
60
60
|
Granular imports are still available (`eidotter/fonts.css`, `eidotter/tokens.css`) if you want to override the font or tokens independently.
|
|
61
61
|
|
|
62
|
+
**Fonts in your build output:** your bundler emits the four JetBrains Mono Nerd Font woff2 files (body tier, about 4 MB) even if you never use the body tier. Browsers only download faces a page uses, so this is deploy size, not page weight. To leave them out, see "Fonts: what your build emits" in [`guidelines/README.md`](guidelines/README.md).
|
|
63
|
+
|
|
62
64
|
**Favicon:** the logomark favicon ships as `eidotter/favicon.svg` (SVG only). Copy it into your site's public dir or point a bundler at it, then `<link rel="icon" href="/favicon.svg" type="image/svg+xml" />`.
|
|
63
65
|
|
|
64
66
|
### With Tailwind
|
|
@@ -79,6 +81,18 @@ module.exports = {
|
|
|
79
81
|
</div>
|
|
80
82
|
```
|
|
81
83
|
|
|
84
|
+
### With Tailwind v4: load eiDotter's CSS in a cascade layer
|
|
85
|
+
|
|
86
|
+
`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:
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
@layer theme, base, components, eidotter, utilities;
|
|
90
|
+
@import "tailwindcss";
|
|
91
|
+
@import "eidotter/styles" layer(eidotter);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
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).
|
|
95
|
+
|
|
82
96
|
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
97
|
|
|
84
98
|
### Server Components (RSC / Next.js App Router)
|
|
@@ -104,7 +118,7 @@ for client apps and `'use client'` files.
|
|
|
104
118
|
|
|
105
119
|
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
120
|
|
|
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.
|
|
121
|
+
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.42.0` (0.41.0 shipped before its review fixes; use the latest 0.41.x, 0.41.3 or later).
|
|
108
122
|
|
|
109
123
|
## Documentation
|
|
110
124
|
|
|
@@ -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,9 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
4
4
|
import { DosDialog } from '../../DosDialog/components/DosDialog.js';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
|
-
|
|
6
|
+
// Each key hint is one no-wrap unit, so a narrow palette wraps between hints
|
|
7
|
+
// ("ESC CLOSE" never splits across lines, DMNC-1612). Text is unchanged.
|
|
8
|
+
const defaultFooterHint = (_jsxs(_Fragment, { children: [_jsx("span", { className: "eidotter-cmdpal__key-hint", children: "\u2191/\u2193 NAVIGATE" }), ' · ', _jsx("span", { className: "eidotter-cmdpal__key-hint", children: "\u21B5 OPEN" }), ' · ', _jsx("span", { className: "eidotter-cmdpal__key-hint", children: "ESC CLOSE" })] }));
|
|
7
9
|
/** Split hotkey string like "mod+shift+p" into a predicate. */
|
|
8
10
|
function parseHotkey(hotkey) {
|
|
9
11
|
const parts = hotkey.toLowerCase().split('+').map(p => p.trim());
|
|
@@ -168,7 +170,7 @@ export const CmdPalette = forwardRef(({ open, onOpenChange, items, placeholder =
|
|
|
168
170
|
if (suppressHoverRef.current)
|
|
169
171
|
return;
|
|
170
172
|
setSelected(i);
|
|
171
|
-
}, onClick: () => handleSelect(item), children: renderItem ? renderItem(item, isSelected) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: cn('eidotter-cmdpal__item-label', isSelected && 'eidotter-cmdpal__text--bright'), children: [_jsx("span", { className: "eidotter-cmdpal__item-caret", "aria-hidden": "true", children: isSelected ? '▸' : ' ' }), item.label] }), item.hint !== undefined && (_jsx("span", { className: cn('eidotter-cmdpal__item-hint', isSelected ? 'eidotter-cmdpal__text--bright' : 'eidotter-cmdpal__text--muted'), children: item.hint }))] })) }, item.id));
|
|
173
|
+
}, onClick: () => handleSelect(item), children: renderItem ? renderItem(item, isSelected) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: cn('eidotter-cmdpal__item-label', isSelected && 'eidotter-cmdpal__text--bright'), children: [_jsx("span", { className: "eidotter-cmdpal__item-caret", "aria-hidden": "true", children: isSelected ? '▸' : ' ' }), _jsx("span", { className: "eidotter-cmdpal__item-text", children: item.label })] }), item.hint !== undefined && (_jsx("span", { className: cn('eidotter-cmdpal__item-hint', isSelected ? 'eidotter-cmdpal__text--bright' : 'eidotter-cmdpal__text--muted'), children: item.hint }))] })) }, item.id));
|
|
172
174
|
})] })] }));
|
|
173
175
|
});
|
|
174
176
|
CmdPalette.displayName = 'CmdPalette';
|
|
@@ -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'
|
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { PressEvent } from 'react-aria-components';
|
|
3
3
|
import type { IconName } from '../../Icon/components/Icon.js';
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `md` = 36×36 hit area, 20px glyph (every built-in adopter). `window` = 28×28,
|
|
6
|
+
* 16px glyph, for dense chrome. `touch` = 44×44, 24px glyph, a full-size touch
|
|
7
|
+
* target for stage and kiosk shells.
|
|
8
|
+
*/
|
|
9
|
+
export type IconButtonSize = 'md' | 'window' | 'touch';
|
|
6
10
|
/** `outline` = 1px square outline at rest (default). `bare` = no resting outline. */
|
|
7
11
|
export type IconButtonVariant = 'outline' | 'bare';
|
|
12
|
+
/**
|
|
13
|
+
* `accent` = the house `text-accent` glyph with the amber hover invert
|
|
14
|
+
* (default). `inherit` = glyph, outline and focus ring take the surrounding
|
|
15
|
+
* `currentColor`, for coloured bars; hover and press tint the button with that
|
|
16
|
+
* colour instead of the amber fill.
|
|
17
|
+
*/
|
|
18
|
+
export type IconButtonTone = 'accent' | 'inherit';
|
|
8
19
|
export interface IconButtonProps {
|
|
9
20
|
/** Glyph from the `<Icon>` set: `Close` (X), `Cancel` (minus, minimise), `Fullscreen` (maximise), `Chevron Left/Right/Up/Down`. */
|
|
10
21
|
icon: IconName;
|
|
11
22
|
/** Accessible name. Required: the button has no visible text. Rendered as `aria-label`. */
|
|
12
23
|
label: string;
|
|
13
|
-
/** Hit-area size. Defaults to `md` (36×36). */
|
|
24
|
+
/** Hit-area size. Defaults to `md` (36×36); `touch` is 44×44. */
|
|
14
25
|
size?: IconButtonSize;
|
|
15
26
|
/**
|
|
16
27
|
* `outline` (default) shows the thin square outline at rest. `bare` drops
|
|
17
28
|
* it, for places where an outline would fight a neighbouring rule.
|
|
18
29
|
*/
|
|
19
30
|
variant?: IconButtonVariant;
|
|
31
|
+
/**
|
|
32
|
+
* Colour source. `accent` (default) is the house look. `inherit` uses the
|
|
33
|
+
* parent's text colour (`currentColor`) for the glyph, outline and focus
|
|
34
|
+
* ring, so the button reads on a coloured status bar; set the bar's `color`.
|
|
35
|
+
*/
|
|
36
|
+
tone?: IconButtonTone;
|
|
20
37
|
/** Disables the button (opacity 0.5, no hover invert). */
|
|
21
38
|
isDisabled?: boolean;
|
|
22
39
|
/** Called on press (mouse, touch, Enter, Space). */
|
|
@@ -15,5 +15,5 @@ import { cn } from '../../../utils/cn.js';
|
|
|
15
15
|
* React Aria `Button` handles press, keyboard and focus-visible. Ref forwards
|
|
16
16
|
* to the `<button>`.
|
|
17
17
|
*/
|
|
18
|
-
export const IconButton = forwardRef(({ icon, label, size = 'md', variant = 'outline', isDisabled, onPress, excludeFromTabOrder, className, }, ref) => (_jsx(AriaButton, { ref: ref, "aria-label": label, isDisabled: isDisabled, onPress: onPress, excludeFromTabOrder: excludeFromTabOrder, className: cn('inline-flex items-center justify-center shrink-0', 'eidotter-icon-button', size === 'window' && 'eidotter-icon-button--window', variant === 'bare' && 'eidotter-icon-button--bare', className), children: _jsx("span", { className: "eidotter-icon-button__glyph", "aria-hidden": "true", children: _jsx(Icon, { name: icon, size: "S", decorative: true }) }) })));
|
|
18
|
+
export const IconButton = forwardRef(({ icon, label, size = 'md', variant = 'outline', tone = 'accent', isDisabled, onPress, excludeFromTabOrder, className, }, ref) => (_jsx(AriaButton, { ref: ref, "aria-label": label, isDisabled: isDisabled, onPress: onPress, excludeFromTabOrder: excludeFromTabOrder, className: cn('inline-flex items-center justify-center shrink-0', 'eidotter-icon-button', size === 'window' && 'eidotter-icon-button--window', size === 'touch' && 'eidotter-icon-button--touch', variant === 'bare' && 'eidotter-icon-button--bare', tone === 'inherit' && 'eidotter-icon-button--inherit', className), children: _jsx("span", { className: "eidotter-icon-button__glyph", "aria-hidden": "true", children: _jsx(Icon, { name: icon, size: "S", decorative: true }) }) })));
|
|
19
19
|
IconButton.displayName = 'IconButton';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { IconButton } from './IconButton.js';
|
|
2
|
-
export type { IconButtonProps, IconButtonSize, IconButtonVariant } from './IconButton.js';
|
|
2
|
+
export type { IconButtonProps, IconButtonSize, IconButtonVariant, IconButtonTone } from './IconButton.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { IconButton } from './components/index.js';
|
|
2
|
-
export type { IconButtonProps, IconButtonSize, IconButtonVariant } from './components/index.js';
|
|
2
|
+
export type { IconButtonProps, IconButtonSize, IconButtonVariant, IconButtonTone } from './components/index.js';
|
|
@@ -18,7 +18,8 @@ export interface LightboxProps {
|
|
|
18
18
|
* The shell supplies the overlay, chrome, motion and the title strip;
|
|
19
19
|
* the `[ n / N ]` counter is the strip's title and the close control sits at
|
|
20
20
|
* its end (an IconButton). Lightbox adds prev/next (IconButtons with the pixel
|
|
21
|
-
* chevrons), caption, keyboard nav, and
|
|
21
|
+
* chevrons; omitted when there is only one image), caption, keyboard nav, and
|
|
22
|
+
* touch swipe. A figure taller than the body scrolls from its top edge.
|
|
22
23
|
* It shares the shell's z-index (`--z-index-modal`) with Modal; stacking a
|
|
23
24
|
* Lightbox over an open Modal is not a supported case.
|
|
24
25
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
4
|
import { IconButton } from '../../IconButton/components/IconButton.js';
|
|
5
5
|
import { DosDialog } from '../../DosDialog/components/DosDialog.js';
|
|
@@ -9,7 +9,8 @@ import { DosDialog } from '../../DosDialog/components/DosDialog.js';
|
|
|
9
9
|
* The shell supplies the overlay, chrome, motion and the title strip;
|
|
10
10
|
* the `[ n / N ]` counter is the strip's title and the close control sits at
|
|
11
11
|
* its end (an IconButton). Lightbox adds prev/next (IconButtons with the pixel
|
|
12
|
-
* chevrons), caption, keyboard nav, and
|
|
12
|
+
* chevrons; omitted when there is only one image), caption, keyboard nav, and
|
|
13
|
+
* touch swipe. A figure taller than the body scrolls from its top edge.
|
|
13
14
|
* It shares the shell's z-index (`--z-index-modal`) with Modal; stacking a
|
|
14
15
|
* Lightbox over an open Modal is not a supported case.
|
|
15
16
|
*
|
|
@@ -96,10 +97,14 @@ export const Lightbox = ({ images, initialIndex = 0, isOpen, onClose, onIndexCha
|
|
|
96
97
|
const current = images[safeIndex];
|
|
97
98
|
const hasPrev = safeIndex > 0;
|
|
98
99
|
const hasNext = safeIndex < images.length - 1;
|
|
100
|
+
// One image: nothing to navigate, so no prev/next at all (two permanently
|
|
101
|
+
// disabled buttons are noise). The `[ 1 / 1 ]` counter stays as the strip
|
|
102
|
+
// title, so the strip keeps its heading and the position stays explicit.
|
|
103
|
+
const canNavigate = images.length > 1;
|
|
99
104
|
return (_jsx(DosDialog, { open: isOpen, onOpenChange: (open) => { if (!open)
|
|
100
105
|
onClose(); }, placement: "center", size: "full", "aria-label": current.alt || 'Image viewer', className: "eidotter-lightbox__container", head: {
|
|
101
106
|
title: (_jsxs("span", { className: "eidotter-lightbox__counter", "aria-live": "polite", children: ["[ ", safeIndex + 1, " / ", images.length, " ]"] })),
|
|
102
107
|
end: (_jsx(IconButton, { icon: "Close", label: "Close lightbox", className: "eidotter-lightbox__close", onPress: onClose })),
|
|
103
|
-
}, children: _jsxs("figure", { className: "eidotter-lightbox__figure", children: [_jsx("img", { className: "eidotter-lightbox__image", src: current.src, alt: current.alt, width: current.width, height: current.height, onTouchStart: onTouchStart, onTouchEnd: onTouchEnd }), _jsx(IconButton, { icon: "Chevron Left", label: "Previous image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--prev", onPress: goPrev, isDisabled: !hasPrev }), _jsx(IconButton, { icon: "Chevron Right", label: "Next image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--next", onPress: goNext, isDisabled: !hasNext }), current.caption && (_jsx("figcaption", { className: "eidotter-lightbox__caption", children: current.caption }))] }) }));
|
|
108
|
+
}, children: _jsxs("figure", { className: "eidotter-lightbox__figure", children: [_jsx("img", { className: "eidotter-lightbox__image", src: current.src, alt: current.alt, width: current.width, height: current.height, onTouchStart: onTouchStart, onTouchEnd: onTouchEnd }), canNavigate && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: "Chevron Left", label: "Previous image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--prev", onPress: goPrev, isDisabled: !hasPrev }), _jsx(IconButton, { icon: "Chevron Right", label: "Next image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--next", onPress: goNext, isDisabled: !hasNext })] })), current.caption && (_jsx("figcaption", { className: "eidotter-lightbox__caption", children: current.caption }))] }) }));
|
|
104
109
|
};
|
|
105
110
|
Lightbox.displayName = 'Lightbox';
|
|
@@ -5,6 +5,24 @@ export interface NavItem {
|
|
|
5
5
|
/** URL/path for the link */
|
|
6
6
|
href: string;
|
|
7
7
|
}
|
|
8
|
+
/** Props eidotter passes to a custom link component. */
|
|
9
|
+
export interface NavLinkProps {
|
|
10
|
+
href: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
'aria-current'?: 'page';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A router link component for `linkComponent`: any component that accepts
|
|
18
|
+
* {@link NavLinkProps}. Its own props may be wider (Next.js `Link` takes
|
|
19
|
+
* `href: string | UrlObject`). Function and class components match
|
|
20
|
+
* `ComponentType`; `forwardRef`/`memo` components such as `next/link` match
|
|
21
|
+
* `ExoticComponent`, which has no `propTypes` member, so the `propTypes`
|
|
22
|
+
* comparison that rejected `next/link` under @types/react 18 does not apply
|
|
23
|
+
* (DMNC-1612). Works with @types/react 18 and 19, no wrapper needed.
|
|
24
|
+
*/
|
|
25
|
+
export type NavLinkComponent = React.ComponentType<NavLinkProps> | React.ExoticComponent<NavLinkProps>;
|
|
8
26
|
export interface NavProps {
|
|
9
27
|
/** Navigation items */
|
|
10
28
|
items: NavItem[];
|
|
@@ -13,15 +31,10 @@ export interface NavProps {
|
|
|
13
31
|
/** Visual variant */
|
|
14
32
|
variant?: 'retro' | 'modern';
|
|
15
33
|
/**
|
|
16
|
-
* Custom link component (e.g
|
|
34
|
+
* Custom link component (e.g. Next.js `Link`, passed directly).
|
|
17
35
|
* If not provided, uses regular anchor tags.
|
|
18
36
|
*/
|
|
19
|
-
linkComponent?:
|
|
20
|
-
href: string;
|
|
21
|
-
className?: string;
|
|
22
|
-
children: React.ReactNode;
|
|
23
|
-
onClick?: () => void;
|
|
24
|
-
}>;
|
|
37
|
+
linkComponent?: NavLinkComponent;
|
|
25
38
|
/** Additional CSS class name */
|
|
26
39
|
className?: string;
|
|
27
40
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Nav, DesktopNav, MobileNav } from './Nav.js';
|
|
2
|
-
export type { NavProps, NavItem } from './Nav.js';
|
|
2
|
+
export type { NavProps, NavItem, NavLinkProps, NavLinkComponent } from './Nav.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Nav, DesktopNav, MobileNav } from './components/index.js';
|
|
2
|
-
export type { NavProps, NavItem } from './components/index.js';
|
|
2
|
+
export type { NavProps, NavItem, NavLinkProps, NavLinkComponent } from './components/index.js';
|
|
@@ -85,9 +85,9 @@ export const RangeBar = forwardRef(({ segments, orientation = 'vertical', size =
|
|
|
85
85
|
[orientation === 'horizontal' ? 'left' : 'top']: `${Math.min(Math.max(target.minShare / (s.weight || 1), 0), 1) * 100}%`,
|
|
86
86
|
} })) }, s.id));
|
|
87
87
|
}) }), _jsx("div", { "aria-hidden": "true", className: cn('eidotter-range-bar__lane flex tabular-nums text-dos-text-accent', orientation === 'horizontal' ? 'flex-row w-full' : 'flex-col'), children: items.map((s) => (_jsxs("div", { className: cn('eidotter-range-bar__cell flex items-center justify-center gap-x-2 gap-y-1 min-w-0', orientation === 'horizontal' ? 'flex-col' : 'flex-row justify-start'), style: { flexGrow: Math.max(s.weight, minSegmentShare), flexBasis: 0 }, children: [s.weight >= labelThreshold && (_jsx("span", { className: "eidotter-range-bar__pct", children: pct(s.weight) })), s.weight >= labelThreshold && target?.segmentId === s.id && ' ', target?.segmentId === s.id && (_jsx("span", { className: "eidotter-range-bar__target-label text-dos-text-brand whitespace-nowrap", children: target.label }))] }, s.id))) })] }), showLegend && (_jsx("ul", { className: "eidotter-range-bar__legend flex flex-col gap-1 list-none p-0 m-0 min-w-0 max-w-full", children: items.map((s) => {
|
|
88
|
-
return (_jsxs("li", { className: "eidotter-range-bar__row
|
|
88
|
+
return (_jsxs("li", { className: "eidotter-range-bar__row flex items-baseline gap-2", children: [_jsx("span", { "aria-hidden": "true", className: cn('eidotter-range-bar__swatch inline-block shrink-0 w-[0.75rem] h-[0.75rem]', toneClasses[s.toneKey], s.patternClass), style: {
|
|
89
89
|
...(s.toneKey === 'neutral' && { '--eidotter-fill': NEUTRAL_FILL }),
|
|
90
|
-
} }), _jsxs("span", { className: "eidotter-range-bar__name text-dos-text-primary min-w-0 break-words", children: [s.label, s.bound && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "eidotter-range-bar__bound text-dos-text-muted whitespace-nowrap", children: s.bound })] }))] }), _jsx("span", { className: "text-dos-text-accent tabular-nums", children: pct(s.weight) }), _jsx("span", { className: "text-dos-text-muted tabular-nums text-right", children: s.duration
|
|
90
|
+
} }), _jsxs("span", { className: "eidotter-range-bar__text flex flex-1 flex-wrap items-baseline gap-x-2 min-w-0", children: [_jsxs("span", { className: "eidotter-range-bar__name text-dos-text-primary flex-auto min-w-0 max-w-full break-words", children: [s.label, s.bound && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "eidotter-range-bar__bound text-dos-text-muted whitespace-nowrap", children: s.bound })] }))] }), _jsxs("span", { className: "eidotter-range-bar__meta ml-auto flex flex-wrap items-baseline justify-end gap-x-2 max-w-full", children: [_jsx("span", { className: "text-dos-text-accent tabular-nums", children: pct(s.weight) }), s.duration && (_jsx("span", { className: "eidotter-range-bar__duration text-dos-text-muted tabular-nums text-right", children: s.duration }))] })] })] }, s.id));
|
|
91
91
|
}) }))] }));
|
|
92
92
|
});
|
|
93
93
|
RangeBar.displayName = 'RangeBar';
|
|
@@ -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
|
};
|
|
@@ -235,6 +235,7 @@ export const componentRegistry = {
|
|
|
235
235
|
react: { path: 'src/components/CommandPrompt', status: 'canonical' },
|
|
236
236
|
},
|
|
237
237
|
changelog: [
|
|
238
|
+
{ version: '0.42.0', type: 'changed', description: 'A placeholder (or unfocused value) wider than the input ends in an ellipsis instead of being cut mid-glyph; a consumer min-width wider than the line still clips (DMNC-1612)' },
|
|
238
239
|
{ version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes' },
|
|
239
240
|
{ version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
|
|
240
241
|
{ version: '0.3.0', type: 'added', description: 'Initial CommandPrompt — prompt prop, onCommand, autoFocus, disabled' },
|
|
@@ -302,6 +303,7 @@ export const componentRegistry = {
|
|
|
302
303
|
since: '0.22.0',
|
|
303
304
|
originNote: 'Fullscreen image viewer on the DosDialog shell. Keyboard nav, prev/next, counter, swipe.',
|
|
304
305
|
changelog: [
|
|
306
|
+
{ version: '0.42.0', type: 'changed', description: 'A figure taller than the body (portrait image at phone width) scrolls from its top edge: the figure is centred with auto block margins instead of justify-content center; a single-image lightbox renders no prev/next, the [ 1 / 1 ] counter stays (DMNC-1612)' },
|
|
305
307
|
{ version: '0.41.0', type: 'changed', description: 'Close and prev/next (md 36px; pixel chevrons replacing the text arrows) are IconButtons; disabled nav opacity 0.5 (was 0.3); keyboard and swipe unchanged (DMNC-1592)' },
|
|
306
308
|
{ version: '0.41.0', type: 'changed', description: 'Rebuilt on DosDialog (size full): gains the title strip (dark, yellow title, dim-amber rule) with the [ n / N ] counter as its title and the Close icon at its end; z-index now --z-index-modal (was 60); panel pops in/out (150ms) (DMNC-1592)' },
|
|
307
309
|
],
|
|
@@ -555,6 +557,7 @@ export const componentRegistry = {
|
|
|
555
557
|
react: { path: 'src/components/Terminal', status: 'canonical' },
|
|
556
558
|
},
|
|
557
559
|
changelog: [
|
|
560
|
+
{ 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)' },
|
|
558
561
|
{ version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes for window controls' },
|
|
559
562
|
{ version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
|
|
560
563
|
{ version: '0.14.0', type: 'changed', description: 'Breaking: minimizable/maximizable/closeable default to false — consumers must opt in' },
|
|
@@ -687,6 +690,8 @@ export const componentRegistry = {
|
|
|
687
690
|
'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' },
|
|
688
691
|
},
|
|
689
692
|
changelog: [
|
|
693
|
+
{ 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)' },
|
|
694
|
+
{ 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)' },
|
|
690
695
|
{ version: '0.41.0', type: 'changed', description: 'Mobile Nav panel close is the shared IconButton (md 36x36, outline, hover invert) (DMNC-1592)' },
|
|
691
696
|
{ 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' },
|
|
692
697
|
{ 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)' },
|
|
@@ -700,6 +705,7 @@ export const componentRegistry = {
|
|
|
700
705
|
consumers: [],
|
|
701
706
|
originNote: 'DesktopNav + MobileNav (MENU trigger + slide-out panel) — retro/modern variants; composed by Header',
|
|
702
707
|
changelog: [
|
|
708
|
+
{ version: '0.42.0', type: 'changed', description: 'linkComponent is typed NavLinkComponent (ComponentType or ExoticComponent of NavLinkProps), so next/link is accepted directly under @types/react 18 and 19; NavLinkProps and NavLinkComponent exported (DMNC-1612)' },
|
|
703
709
|
{ version: '0.41.0', type: 'changed', description: 'Retro variant: desktop links, mobile panel links and the MENU trigger in brand amber with bright-brand-amber hover (no glow on the trigger); desktop and panel link labels render uppercase DOS. Modern variant unchanged (DMNC-1584)' },
|
|
704
710
|
],
|
|
705
711
|
},
|
|
@@ -753,6 +759,7 @@ export const componentRegistry = {
|
|
|
753
759
|
},
|
|
754
760
|
platforms: { react: { path: 'src/components/DosDialog', status: 'canonical' } },
|
|
755
761
|
changelog: [
|
|
762
|
+
{ 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)' },
|
|
756
763
|
{ 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
764
|
{ 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)' },
|
|
758
765
|
{ 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' },
|
|
@@ -768,9 +775,13 @@ export const componentRegistry = {
|
|
|
768
775
|
'size:window': { description: '28x28, 16px glyph, for dense chrome (no built-in adopter)', since: '0.41.0' },
|
|
769
776
|
'variant:outline': { description: '1px border-default outline at rest (default)', since: '0.41.0' },
|
|
770
777
|
'variant:bare': { description: 'No resting outline (Tag remove)', since: '0.41.0' },
|
|
778
|
+
'size:touch': { description: '44x44 touch target, 24px glyph, for stage and kiosk shells', since: '0.42.0' },
|
|
779
|
+
'tone:accent': { description: 'text-accent glyph, amber hover invert (default)', since: '0.42.0' },
|
|
780
|
+
'tone:inherit': { description: 'Glyph, outline and focus ring take the surrounding currentColor; hover tints with it, for coloured bars', since: '0.42.0' },
|
|
771
781
|
},
|
|
772
782
|
platforms: { react: { path: 'src/components/IconButton', status: 'canonical' } },
|
|
773
783
|
changelog: [
|
|
784
|
+
{ version: '0.42.0', type: 'added', description: 'size touch (44x44, 24px glyph) and tone prop: accent (default, unchanged) or inherit (currentColor glyph, outline and focus ring; hover tints with currentColor) for coloured bars (DMNC-1612)' },
|
|
774
785
|
{ version: '0.41.0', type: 'added', description: 'Initial IconButton: icon, required label (aria-label), size md/window, variant outline/bare, isDisabled, onPress, excludeFromTabOrder; adopted by Modal, Lightbox, Notification, Alert, Tag and Nav (DMNC-1592)' },
|
|
775
786
|
],
|
|
776
787
|
},
|
|
@@ -806,6 +817,7 @@ export const componentRegistry = {
|
|
|
806
817
|
originNote: '⌘K / Ctrl+K command palette overlay. Built on React Aria ModalOverlay/Modal/Dialog (same primitives as Modal). Generic items + scored search + keyboard navigation.',
|
|
807
818
|
platforms: { react: { path: 'src/components/CmdPalette', status: 'canonical' } },
|
|
808
819
|
changelog: [
|
|
820
|
+
{ version: '0.42.0', type: 'changed', description: 'Row label keeps every character (label text in eidotter-cmdpal__item-text); the hint shrinks first and truncates with an ellipsis; default footer wraps between key hints, never inside ESC CLOSE; narrow rules are a container query on the palette (DMNC-1612)' },
|
|
809
821
|
{ version: '0.41.0', type: 'changed', description: 'Rebuilt on DosDialog (top placement, lg width, decorative strip); the JUMP TO head loses its amber fill (dark strip, yellow text); placeholder and row hints use text-muted, the selected row label and hint text-brand; the panel pops in/out (150ms) instead of the 8px drop; own cmdpal-enter/exit and backdrop keyframes removed (DMNC-1592)' },
|
|
810
822
|
{ version: '0.20.0', type: 'added', description: 'Initial CmdPalette — generic items with keywords, mod+k global hotkey, custom renderItem, useId() per-instance DOM ids, selected-clamp on items mutation' },
|
|
811
823
|
{ version: '0.41.0', type: 'changed', description: 'Fix: search input shows the 2px focus-visible ring instead of only a faint background tint (DMNC-1436)' },
|
|
@@ -1021,6 +1033,7 @@ export const componentRegistry = {
|
|
|
1021
1033
|
changelog: [
|
|
1022
1034
|
{ version: '0.40.0', type: 'added', description: 'Initial RangeBar (health metabolism family)' },
|
|
1023
1035
|
{ 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)' },
|
|
1036
|
+
{ version: '0.42.0', type: 'changed', description: 'Legend name wins width: name and the percent/duration group share a wrapping line, so percent and duration move under the name (right-aligned) before the name wraps, and a word breaks only when it alone is wider than the row; no empty duration cell; eidotter-range-bar__text, __meta and __duration hooks (DMNC-1612)' },
|
|
1024
1037
|
],
|
|
1025
1038
|
},
|
|
1026
1039
|
SeriesBars: {
|