@setsquare/ui 0.8.0 → 0.9.1
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/CHANGELOG.md +71 -0
- package/README.md +3 -3
- package/dist/lib/rail-name.d.ts +40 -0
- package/dist/lib/rail-name.d.ts.map +1 -0
- package/dist/lib/rail-name.js +47 -0
- package/dist/lib/rail-name.js.map +1 -0
- package/dist/lib/soft-hyphen.d.ts +24 -0
- package/dist/lib/soft-hyphen.d.ts.map +1 -0
- package/dist/lib/soft-hyphen.js +67 -0
- package/dist/lib/soft-hyphen.js.map +1 -0
- package/dist/lib/use-hyphenated.d.ts +22 -0
- package/dist/lib/use-hyphenated.d.ts.map +1 -0
- package/dist/lib/use-hyphenated.js +81 -0
- package/dist/lib/use-hyphenated.js.map +1 -0
- package/dist/shell/rail.d.ts +5 -1
- package/dist/shell/rail.d.ts.map +1 -1
- package/dist/shell/rail.js +55 -2
- package/dist/shell/rail.js.map +1 -1
- package/dist/styles/theme.css +11 -2
- package/dist/testing/index.d.ts +6 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +14 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +24 -2
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +8 -1
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/tree.d.ts.map +1 -1
- package/dist/ui/tree.js +8 -3
- package/dist/ui/tree.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1 — 2026-10-15
|
|
4
|
+
|
|
5
|
+
- **Fix: a Tree row's title or description no longer breaks between two letters without a hyphen.** They had
|
|
6
|
+
`overflow-wrap: anywhere`, so a word too wide for the row was split wherever it ran out of room, with no hyphen,
|
|
7
|
+
as the Rail's names were before 0.9.0. Now a word too wide breaks with a visible hyphen: where the browser can
|
|
8
|
+
hyphenate it, at a soft hyphen (U+00AD) a product wrote in it, and otherwise at the last letter that fits, three
|
|
9
|
+
letters either side at least. A word that fits a line is left whole. The row's accessible name and `textValue` leave
|
|
10
|
+
soft hyphens out. It reads each row's own layout after it renders and when the row resizes, the title turns bold or
|
|
11
|
+
text spacing changes; it adds no layout of its own, so a long outline costs nothing extra.
|
|
12
|
+
- **Fix: a FileUpload file name no longer breaks between two letters without a mark.** It had `overflow-wrap:
|
|
13
|
+
anywhere`. A file name has to read exactly, so a hyphen added at a break would look like part of it. It now breaks
|
|
14
|
+
where it divides itself, with nothing added: after `_`, `-`, `.` and similar, at a change from lower to upper case,
|
|
15
|
+
and between letters and digits (`<wbr>`). A run too wide for the row with none of those breaks at the last letter
|
|
16
|
+
that fits, marked ↩ by `hyphenate-character`. When a name needs that mark, what is seen is hidden from assistive
|
|
17
|
+
technology and a plain copy is said, and copying the name copies it without the soft hyphens (Chrome would copy
|
|
18
|
+
them).
|
|
19
|
+
- **Fix: a vertical Tabs rail's name no longer breaks between two letters without a hyphen.** The rail's names had
|
|
20
|
+
`hyphens: auto` and `overflow-wrap: anywhere`, so a capitalised English word too wide for the tile, which Chrome and
|
|
21
|
+
Firefox won't hyphenate, was split silently. Now a word too wide breaks with a visible hyphen: the browser's, or at
|
|
22
|
+
the last letter that fits. The name and its hidden bold copy show the same text, so they break alike, and the grid
|
|
23
|
+
holding them is capped at the tile's width. With soft hyphens added, the tab is named by a plain copy. The Tabs
|
|
24
|
+
and TabsRail READMEs say so; they used to say the names were "hyphenated and wrapping anywhere".
|
|
25
|
+
- **Fix: the Rail's soft hyphens and the docs example's text.** The formatter had turned the `\u00AD` escapes in
|
|
26
|
+
three files into invisible characters, so the Rail page's example on ui.setsquare.dev read "written Storyboard"
|
|
27
|
+
where it meant to show `Story\u00ADboard`. Soft hyphens in the source are now written by their code point
|
|
28
|
+
(`String.fromCodePoint(0xad)`), which no formatter changes.
|
|
29
|
+
- **Tests.** Unit tests for the shared soft-hyphen helpers, and `test/browser/controls.mjs` renders a 220px Tree at AA
|
|
30
|
+
and AAA: German compounds no width fits, a selected (bold) row, a product's soft hyphen, a long word in a
|
|
31
|
+
description and a title whose words fit. No word breaks without a hyphen, nothing runs out of its row, the words
|
|
32
|
+
that fit get no soft hyphens, and rows are named without them; again with 1.4.12's text spacing. It also renders a
|
|
33
|
+
240px FileUpload at AA and AAA with names that divide at separators and at case and digit changes, and one with a
|
|
34
|
+
run nothing divides: they break only where they divide or at a soft hyphen drawn as ↩, nothing runs out, only the
|
|
35
|
+
run is marked, every name is said and copied plain; again with text spacing. Unit tests for `fileNameParts`.
|
|
36
|
+
`test/browser/tabs.mjs` renders a vertical rail in text display with German compounds and English words, at AA and
|
|
37
|
+
AAA: nothing runs out of its tile, both copies match, a word breaks only at a soft hyphen or between words, and
|
|
38
|
+
every tab is named plainly; again with text spacing.
|
|
39
|
+
|
|
40
|
+
## 0.9.0 — 2026-10-15
|
|
41
|
+
|
|
42
|
+
- **`findClippedText` waits two animation frames before it measures, and returns a Promise.** It applied 1.4.12's
|
|
43
|
+
spacing and measured in the same instant, so a component that adapts in a ResizeObserver was caught halfway: the
|
|
44
|
+
rail's names below spilled for the one frame before they refitted, and the check reported it, though no reader
|
|
45
|
+
would see it. It now measures as a reader sees the page, two frames after the spacing goes on (or after 100ms in a
|
|
46
|
+
hidden document, where frames don't run). **Breaking for a script that calls it synchronously in the page:** it
|
|
47
|
+
returns a Promise now. Through `page.evaluate`, which awaits it, and `expectTextUnclipped`, nothing changes.
|
|
48
|
+
- **Fix: a rail name no longer breaks between two letters without a hyphen.** In Chrome, "Storyboard" at AA showed as
|
|
49
|
+
"Storyboar" / "d". Chromium (and Firefox) never hyphenate an English word that starts with a capital letter, and
|
|
50
|
+
every rail name starts with one, so `hyphens: auto` found no break point and `overflow-wrap: anywhere` split the word
|
|
51
|
+
wherever it ran out of room. en-GB wasn't the cause: Chrome on macOS hyphenates en-GB and en-US alike, and knows
|
|
52
|
+
"storyboard" in lower case. Now:
|
|
53
|
+
- a word too wide for its tile shrinks the whole name, to 11.5px at the least, sized for the bold of the current
|
|
54
|
+
page so an item keeps its shape when it becomes current. In Atkinson Hyperlegible Next, "Storyboard" fits one line
|
|
55
|
+
at 11.75px at AA. 11.5px is the rail's one exception to the 13px floor, and the design-system README says so;
|
|
56
|
+
- a word too wide even at 11.5px breaks with a visible hyphen at 13px: where the browser can hyphenate it (German
|
|
57
|
+
under `lang="de"`, or WebKit), and otherwise at the last letter that fits, three letters either side at least;
|
|
58
|
+
- a soft hyphen (U+00AD) a product writes in a name is where it breaks: "Story\u00ADboard" fits whole where it can,
|
|
59
|
+
and is "Story-" / "board" where it can't. The Rail README says to do this for words a dictionary won't know;
|
|
60
|
+
- the item's accessible name is the plain name, without soft hyphens (Chrome keeps them in a link's name). The name
|
|
61
|
+
seen is now hidden from assistive technology and the name said is a visually hidden copy, with the detail and
|
|
62
|
+
status after it as before;
|
|
63
|
+
- the fit is measured in the browser after the item renders, and again when the rail's width, zoom, text spacing
|
|
64
|
+
or the fonts change. The server's HTML has the name at 13px, unfitted.
|
|
65
|
+
- **Docs.** A RailNames card (long single words, a soft hyphen, German) and the same example on the Rail page of
|
|
66
|
+
ui.setsquare.dev, under the shell frames.
|
|
67
|
+
- **Tests.** Unit tests for the fitting (`src/lib/rail-name.ts`). `test/browser/rail.mjs` renders "Storyboard", "Art
|
|
68
|
+
direction", "Brand kits", "Voiceover", "Settings", "Einstellungen", "Sprachausgabe" and "Story\u00ADboard", plain and
|
|
69
|
+
current, in English and German, in all eight theme and brand combinations: no break inside a word without a hyphen,
|
|
70
|
+
nothing out of its tile, never under 11.5px, two lines at most, the soft hyphen kept and the plain accessible name,
|
|
71
|
+
and again under 1.4.12 text spacing and at 200% zoom. The suite renders in the system font; the outcomes in
|
|
72
|
+
Atkinson Hyperlegible Next were checked by hand in Chrome 155 on macOS.
|
|
73
|
+
|
|
3
74
|
## 0.8.0 — 2026-10-14
|
|
4
75
|
|
|
5
76
|
- **Search finds items by words that aren't in their label.** `SearchItem` takes `keywords?: string[]`: what people
|
package/README.md
CHANGED
|
@@ -332,7 +332,7 @@ test('settings page', async ({ page }) => {
|
|
|
332
332
|
- `findInvisibleFocus(page)` focuses every control a keyboard reaches, one at a time, and compares a screenshot of it with focus on it and with focus on another control. Any that didn't change have no visible focus indicator (2.4.7). Tooltips, transitions and the caret are turned off for the comparison. A control that focus can't leave (the only one in a modal) comes back as `trapped` rather than passed. `{ ignore }` leaves controls unchecked, such as parts checked on another page, while focus can still move to them. `expectFocusVisible(page)` throws for the rest.
|
|
333
333
|
- `findUndistinguishedLinks(options?)` lists links in running text (laid out inline, in a block with words of its own) that nothing but colour marks, at under 3:1 against the text around them — WCAG's F73. An underline or bottom border, bold or italics the surrounding text doesn't have, or 3:1 passes. `{ underline: true }` holds links to this package's rule instead: underlined. `expectLinksDistinguished(page, options?)` throws with them.
|
|
334
334
|
- `findPageOverflow(options?)` lists everything that reaches past the right edge of the page at the width it is laid out at: `scrolls` when the page scrolls sideways because of it, `clipped` when an `overflow: hidden` or `clip` box at the page's edge cuts off something with words or media in it. Content in a scroller of its own that fits (a `ScrollRegion`, a code block), content clipped inside a component, and fixed-position elements are left out, so a decorative glow bleeding off the edge isn't reported. Set the viewport to 320px for WCAG 1.4.10's reflow width. `expectPageReflows(page, options?)` throws with them.
|
|
335
|
-
- `findClippedText(options?)` applies WCAG 1.4.12's four spacing metrics to the page (line height 1.5, 2em after paragraphs, letter spacing 0.12em, word spacing 0.16em) and lists every box whose content now runs out of it further than before: `clipped` for one that hides its overflow or truncates, `overflows` for one with a set size that the text spills out of. Scroll containers are left out, since their content can still be scrolled to, and so is text that runs out of a box inside a scroller that scrolls that way (code in a code block). The spacing comes off again afterwards. `expectTextUnclipped(page, options?)` throws with them.
|
|
335
|
+
- `findClippedText(options?)` applies WCAG 1.4.12's four spacing metrics to the page (line height 1.5, 2em after paragraphs, letter spacing 0.12em, word spacing 0.16em) and lists every box whose content now runs out of it further than before: `clipped` for one that hides its overflow or truncates, `overflows` for one with a set size that the text spills out of. Scroll containers are left out, since their content can still be scrolled to, and so is text that runs out of a box inside a scroller that scrolls that way (code in a code block). It measures two animation frames after the spacing goes on, so a component that adapts in a ResizeObserver (a rail name refitting its tile) is measured as a reader sees it, not halfway; it returns a Promise, which `page.evaluate` awaits. The spacing comes off again afterwards. `expectTextUnclipped(page, options?)` throws with them.
|
|
336
336
|
- `findEndlessAnimations(options?)` lists what moves for longer than five seconds (`max`), or forever, with nothing to pause it: the CSS animations, CSS transitions and Web Animations running now (`document.getAnimations()`), and playing videos without `controls`, whether or not they are `aria-hidden`. Something is paused by a control when it sits inside an element whose `id` a visible button names in `data-pauses`. It can't see motion drawn with `requestAnimationFrame`, SVG animation, animated images or canvas, and only sees what is running when it is called. A duration comes back as `Infinity` for anything that repeats forever. `expectAnimationsPausable(page, options?)` throws with them.
|
|
337
337
|
- `findCursorMismatches(page, options?)` hovers every target and reads the cursor at its centre and just inside each corner, so the whole target is checked, not only its paint. Each kind has its expected cursors (`press`: `pointer`; `text`: `text` or `auto`; `drag`: `grab`; `disabled`: `not-allowed`; `pending`: `wait` or `progress`; `readonly` and `current`: `default`); `expect` changes them, and `{ press: ['default'] }` checks a product that keeps the arrow on buttons. `expectCursorsMatch(page, options?)` throws with them.
|
|
338
338
|
|
|
@@ -350,7 +350,7 @@ The positioning, target, link, reflow, text-spacing and motion scans run in the
|
|
|
350
350
|
- **Browser (Chromium only):** the design-system previews on React 19, loaded as the design-system artifact loads them:
|
|
351
351
|
- axe-core on every preview in all four themes;
|
|
352
352
|
- the search's ⌘K / Ctrl+K on a page of its own: left to text fields, text areas, editors and a handler that took the key; one search on a page holding it, showing it and setting `aria-keyshortcuts`, and the next taking over; `shortcut={false}`; the console error for an item id used twice; and `aria-keyshortcuts` reaching the element through `Button`, `IconButton`, `LinkButton` and `Tree`, which React Aria's own components drop, and the page builder's Undo, Redo and outline saying their keys;
|
|
353
|
-
- keyboard and focus behaviour for the dashboard shell (with its search finding by keyword, in ranked order), forms, feedback states, and the slider, date, file and selection controls;
|
|
353
|
+
- keyboard and focus behaviour for the dashboard shell (with its search finding by keyword, in ranked order), forms, feedback states, and the slider, date, file and selection controls (a narrow Tree's long words breaking only with a hyphen, and its rows named without soft hyphens; a narrow FileUpload's names breaking only where they divide themselves or at a ↩ mark, said and copied plain; a vertical Tabs rail's long names breaking only with a hyphen; each with and without text spacing);
|
|
354
354
|
- the site shell: the phone menu opening, pushing the page down and closing on Escape from the panel or the button; the current page's weight and marker; one order for the header, the phone menu and the footer; a sticky header covering no focused control; and the header fitting at seven widths from 320px to 1280px, either side of `lg` among them;
|
|
355
355
|
- the motion rule on the Hero card's media: the motion check exempts it only for its visible pause control, Pause stops it, reduced motion never starts it, and nothing waits hidden for it;
|
|
356
356
|
- code blocks copying, announcing a second copy, a terminal copying its commands only, and a refused copy; the theme switcher's choice, System following the operating system, the level, and the theme script running under a CSP that allows only its published hash;
|
|
@@ -361,7 +361,7 @@ The positioning, target, link, reflow, text-spacing and motion scans run in the
|
|
|
361
361
|
- every control a keyboard reaches visibly changing when it takes focus, in two themes;
|
|
362
362
|
- every link in running text underlined, and every target showing the cursor the rule gives it, across its whole area;
|
|
363
363
|
- toasts measured against the focused control;
|
|
364
|
-
- the `Rail`, in all four themes and two brands: `--rail-width` at AA and AAA, every target at least `--target-min`, "Art direction" and "Brand kits" in two lines or fewer (in the system face the suites render with: they load no web fonts), no name cut off, the current page's weight, fill, edge, and icon and name in brand, groups named by hidden headings with a rule between them, the footer pinned while the items scroll, no transition or animation on hover or focus but a working badge's pulse, the current page's marker in `Highlight` under Chromium's emulated forced colours, no collapse toggle at 1280px and 768px, the phone's Sheet from the menu button with the items as rows and focus back on the button after Escape, and the Sidebar's shell keeping its toggle;
|
|
364
|
+
- the `Rail`, in all four themes and two brands: `--rail-width` at AA and AAA, every target at least `--target-min`, "Art direction" and "Brand kits" in two lines or fewer (in the system face the suites render with: they load no web fonts), no name cut off; long single words ("Storyboard", "Einstellungen", "Sprachausgabe", and "Story\u00ADboard" with a soft hyphen), plain and current, in English and German, never broken between two letters without a hyphen, never under 11.5px, two lines at most and named without the soft hyphen, again under text spacing and at 200% zoom; the current page's weight, fill, edge, and icon and name in brand, groups named by hidden headings with a rule between them, the footer pinned while the items scroll, no transition or animation on hover or focus but a working badge's pulse, the current page's marker in `Highlight` under Chromium's emulated forced colours, no collapse toggle at 1280px and 768px, the phone's Sheet from the menu button with the items as rows and focus back on the button after Escape, and the Sidebar's shell keeping its toggle;
|
|
365
365
|
- the current page marked alike in a `Rail`, a `SidebarItem` and a `SidebarDisclosure`'s page: the fill, the 3px edge, and the name in brand at 700, the same when hovered, and a bar in `Highlight` under emulated forced colours;
|
|
366
366
|
- live status on nav items, in all four themes and two brands: working a hollow ring and attention a dot in a ring, each in its colour, with a cut-out that matches the ground at rest, hovered, current and current while hovered; working pulsing three times at AA and not at all at AAA or under reduced motion, attention never moving, and nothing on a badge moving after five seconds; names with the status, detail and warning words; details in tabular numbers, under the name in a rail and right-aligned with a count in the sidebar, the warning one with its icon and colour; a product's own drawing as the icon at 20px; the collapsed sidebar's tooltips and badges; and both badges in `CanvasText` and `Highlight` under Chromium's emulated forced colours;
|
|
367
367
|
- tabs: the arrow keys in a row and a rail, Home and End, names with counts and badges, tooltips on hover (after 500ms, hoverable, closed by Escape and by a press) and on keyboard focus, the rail becoming a row below 480px, the three `hiddenPanels` modes (a draft surviving, a hidden panel's effects stopping, a Suspense read loading while pre-rendered), an important badge's pulse ending inside five seconds and replaying when its count rises, clear-on-open calling `onBadgeClear`, only opacity and colour changing at AAA, nothing animating under reduced motion, and no badge, ring or halo over any text in all three displays at full width and 360px;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a rail name fits its tile: whole words on at most two lines, shrunk no further than a floor, and a word that
|
|
3
|
+
* still doesn't fit broken with a visible hyphen, never between two letters without one.
|
|
4
|
+
*
|
|
5
|
+
* Why it can't be left to `hyphens: auto`: Chromium never hyphenates a capitalised word in English (it takes it for
|
|
6
|
+
* a proper noun; `Hyphenation::ShouldHyphenateWord`), and Firefox doesn't either, so "Storyboard" gets no break
|
|
7
|
+
* point at all. `overflow-wrap: anywhere` then split it as "Storyboar" / "d".
|
|
8
|
+
*/
|
|
9
|
+
import { SOFT_HYPHEN, hyphenateEverywhere, withoutSoftHyphens } from './soft-hyphen.js';
|
|
10
|
+
export { SOFT_HYPHEN, hyphenateEverywhere };
|
|
11
|
+
/** The name as said and as matched: a product's soft hyphens taken out. */
|
|
12
|
+
export declare const plainName: typeof withoutSoftHyphens;
|
|
13
|
+
/** The size the name is set in, and the floor it may shrink to: the rail's one exception to the 13px rule. */
|
|
14
|
+
export declare const RAIL_NAME_SIZE = 13;
|
|
15
|
+
export declare const RAIL_NAME_FLOOR = 11.5;
|
|
16
|
+
/** What laying the name out would give: whether a word runs out of the box, and how many lines it takes. */
|
|
17
|
+
export interface NameLayout {
|
|
18
|
+
overflows: boolean;
|
|
19
|
+
lines: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Lays `text` out at `size` px in the name's box and says how it went. `whole`: words never break inside (soft
|
|
23
|
+
* hyphens taken out, no hyphenation), so it answers "do the words fit whole?". `hyphenate`: as the page sets it,
|
|
24
|
+
* with the browser's own hyphenation and any soft hyphens in `text`.
|
|
25
|
+
*/
|
|
26
|
+
export type MeasureName = (size: number, text: string, mode: 'whole' | 'hyphenate') => NameLayout;
|
|
27
|
+
export interface FittedName {
|
|
28
|
+
/** The font size in px. */
|
|
29
|
+
size: number;
|
|
30
|
+
/** The text to show: the name, with soft hyphens added to any word that has to break. */
|
|
31
|
+
text: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The largest size from 13px down to the floor at which every word fits whole on at most two lines. Failing that,
|
|
35
|
+
* the largest at which every word fits whole, on more lines. Failing that, a word is too long at any size allowed:
|
|
36
|
+
* back to 13px (shrinking doesn't save the break), broken where the browser can (its dictionary, a product's soft
|
|
37
|
+
* hyphen), and otherwise at the last place that fits.
|
|
38
|
+
*/
|
|
39
|
+
export declare function fitRailName(name: string, measure: MeasureName): FittedName;
|
|
40
|
+
//# sourceMappingURL=rail-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail-name.d.ts","sourceRoot":"","sources":["../../src/lib/rail-name.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;AAE5C,2EAA2E;AAC3E,eAAO,MAAM,SAAS,2BAAqB,CAAC;AAE5C,8GAA8G;AAC9G,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,eAAe,OAAO,CAAC;AAIpC,4GAA4G;AAC5G,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,WAAW,KAAK,UAAU,CAAC;AAElG,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,UAAU,CAqB1E"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a rail name fits its tile: whole words on at most two lines, shrunk no further than a floor, and a word that
|
|
3
|
+
* still doesn't fit broken with a visible hyphen, never between two letters without one.
|
|
4
|
+
*
|
|
5
|
+
* Why it can't be left to `hyphens: auto`: Chromium never hyphenates a capitalised word in English (it takes it for
|
|
6
|
+
* a proper noun; `Hyphenation::ShouldHyphenateWord`), and Firefox doesn't either, so "Storyboard" gets no break
|
|
7
|
+
* point at all. `overflow-wrap: anywhere` then split it as "Storyboar" / "d".
|
|
8
|
+
*/
|
|
9
|
+
import { SOFT_HYPHEN, hyphenateEverywhere, withoutSoftHyphens } from './soft-hyphen.js';
|
|
10
|
+
export { SOFT_HYPHEN, hyphenateEverywhere };
|
|
11
|
+
/** The name as said and as matched: a product's soft hyphens taken out. */
|
|
12
|
+
export const plainName = withoutSoftHyphens;
|
|
13
|
+
/** The size the name is set in, and the floor it may shrink to: the rail's one exception to the 13px rule. */
|
|
14
|
+
export const RAIL_NAME_SIZE = 13;
|
|
15
|
+
export const RAIL_NAME_FLOOR = 11.5;
|
|
16
|
+
const STEP = 0.25;
|
|
17
|
+
const MAX_LINES = 2;
|
|
18
|
+
/**
|
|
19
|
+
* The largest size from 13px down to the floor at which every word fits whole on at most two lines. Failing that,
|
|
20
|
+
* the largest at which every word fits whole, on more lines. Failing that, a word is too long at any size allowed:
|
|
21
|
+
* back to 13px (shrinking doesn't save the break), broken where the browser can (its dictionary, a product's soft
|
|
22
|
+
* hyphen), and otherwise at the last place that fits.
|
|
23
|
+
*/
|
|
24
|
+
export function fitRailName(name, measure) {
|
|
25
|
+
const plain = plainName(name);
|
|
26
|
+
let wholeOnMoreLines;
|
|
27
|
+
for (let size = RAIL_NAME_SIZE; size >= RAIL_NAME_FLOOR - 1e-9; size -= STEP) {
|
|
28
|
+
const at = measure(size, plain, 'whole');
|
|
29
|
+
if (at.overflows)
|
|
30
|
+
continue;
|
|
31
|
+
if (at.lines <= MAX_LINES)
|
|
32
|
+
return { size, text: name };
|
|
33
|
+
wholeOnMoreLines ??= size;
|
|
34
|
+
}
|
|
35
|
+
if (wholeOnMoreLines !== undefined)
|
|
36
|
+
return { size: wholeOnMoreLines, text: name };
|
|
37
|
+
if (!measure(RAIL_NAME_SIZE, name, 'hyphenate').overflows)
|
|
38
|
+
return { size: RAIL_NAME_SIZE, text: name };
|
|
39
|
+
const text = name
|
|
40
|
+
.split(' ')
|
|
41
|
+
.map((word) => measure(RAIL_NAME_SIZE, plainName(word), 'whole').overflows
|
|
42
|
+
? hyphenateEverywhere(word)
|
|
43
|
+
: word)
|
|
44
|
+
.join(' ');
|
|
45
|
+
return { size: RAIL_NAME_SIZE, text };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=rail-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail-name.js","sourceRoot":"","sources":["../../src/lib/rail-name.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;AAE5C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAE5C,8GAA8G;AAC9G,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,IAAI,GAAG,IAAI,CAAC;AAClB,MAAM,SAAS,GAAG,CAAC,CAAC;AAsBpB;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAoB;IAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,gBAAoC,CAAC;IACzC,KAAK,IAAI,IAAI,GAAG,cAAc,EAAE,IAAI,IAAI,eAAe,GAAG,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;QAC7E,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,EAAE,CAAC,SAAS;YAAE,SAAS;QAC3B,IAAI,EAAE,CAAC,KAAK,IAAI,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvD,gBAAgB,KAAK,IAAI,CAAC;IAC5B,CAAC;IACD,IAAI,gBAAgB,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClF,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,SAAS;QACvD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,IAAI;SACd,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,OAAO,CAAC,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,SAAS;QACzD,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC;QAC3B,CAAC,CAAC,IAAI,CACT;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Soft hyphens (U+00AD): where a long word may break, with a hyphen shown only when it does. */
|
|
2
|
+
/** U+00AD, written by its code point so no formatter turns it into an invisible character in the source. */
|
|
3
|
+
export declare const SOFT_HYPHEN: string;
|
|
4
|
+
/** The text as said and as matched: soft hyphens taken out. */
|
|
5
|
+
export declare const withoutSoftHyphens: (text: string) => string;
|
|
6
|
+
/**
|
|
7
|
+
* A soft hyphen at every place in `word` with at least three letters either side, so the browser breaks it, with a
|
|
8
|
+
* hyphen, at the last place that fits whatever the width, spacing or zoom. A word that already has one of a
|
|
9
|
+
* product's is left as it is.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hyphenateEverywhere(word: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* `text` with soft hyphens added to each word wider than the line, by `widthOf`, so nothing breaks between two
|
|
14
|
+
* letters without a hyphen. A word that fits a line is left alone, so it wraps whole rather than being hyphenated
|
|
15
|
+
* at a line's end.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hyphenateTooWide(text: string, lineWidth: number, widthOf: (word: string, start: number) => number): string;
|
|
18
|
+
/**
|
|
19
|
+
* A file name in the pieces it divides itself into, so it can break between them with nothing added: after a
|
|
20
|
+
* separator (`_`, `-`, `.` and the like), at a change from lower to upper case ("floorPlan"), and between letters
|
|
21
|
+
* and digits ("IMG20261014"). Joined, the pieces are the name.
|
|
22
|
+
*/
|
|
23
|
+
export declare function fileNameParts(name: string): string[];
|
|
24
|
+
//# sourceMappingURL=soft-hyphen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soft-hyphen.d.ts","sourceRoot":"","sources":["../../src/lib/soft-hyphen.ts"],"names":[],"mappings":"AAAA,iGAAiG;AAEjG,4GAA4G;AAC5G,eAAO,MAAM,WAAW,QAA6B,CAAC;AAEtD,+DAA+D;AAC/D,eAAO,MAAM,kBAAkB,SAAU,MAAM,WAAqC,CAAC;AAKrF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,UAWjD;AAKD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBpD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Soft hyphens (U+00AD): where a long word may break, with a hyphen shown only when it does. */
|
|
2
|
+
/** U+00AD, written by its code point so no formatter turns it into an invisible character in the source. */
|
|
3
|
+
export const SOFT_HYPHEN = String.fromCodePoint(0xad);
|
|
4
|
+
/** The text as said and as matched: soft hyphens taken out. */
|
|
5
|
+
export const withoutSoftHyphens = (text) => text.replaceAll(SOFT_HYPHEN, '');
|
|
6
|
+
/** The fewest letters either side of a hyphen this adds. */
|
|
7
|
+
const EDGE = 3;
|
|
8
|
+
/**
|
|
9
|
+
* A soft hyphen at every place in `word` with at least three letters either side, so the browser breaks it, with a
|
|
10
|
+
* hyphen, at the last place that fits whatever the width, spacing or zoom. A word that already has one of a
|
|
11
|
+
* product's is left as it is.
|
|
12
|
+
*/
|
|
13
|
+
export function hyphenateEverywhere(word) {
|
|
14
|
+
if (word.includes(SOFT_HYPHEN))
|
|
15
|
+
return word;
|
|
16
|
+
const letters = [...word];
|
|
17
|
+
if (letters.length < EDGE * 2)
|
|
18
|
+
return word;
|
|
19
|
+
return letters
|
|
20
|
+
.map((letter, i) => (i >= EDGE && i <= letters.length - EDGE ? SOFT_HYPHEN : '') + letter)
|
|
21
|
+
.join('');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `text` with soft hyphens added to each word wider than the line, by `widthOf`, so nothing breaks between two
|
|
25
|
+
* letters without a hyphen. A word that fits a line is left alone, so it wraps whole rather than being hyphenated
|
|
26
|
+
* at a line's end.
|
|
27
|
+
*/
|
|
28
|
+
export function hyphenateTooWide(text, lineWidth, widthOf) {
|
|
29
|
+
let at = 0;
|
|
30
|
+
return text
|
|
31
|
+
.split(' ')
|
|
32
|
+
.map((word) => {
|
|
33
|
+
const start = at;
|
|
34
|
+
at += word.length + 1;
|
|
35
|
+
return widthOf(word, start) > lineWidth + 0.5 ? hyphenateEverywhere(word) : word;
|
|
36
|
+
})
|
|
37
|
+
.join(' ');
|
|
38
|
+
}
|
|
39
|
+
/** The characters a file name divides itself at: a break may follow each. */
|
|
40
|
+
const SEPARATOR = /[_\-.,+~@/\\]/u;
|
|
41
|
+
/**
|
|
42
|
+
* A file name in the pieces it divides itself into, so it can break between them with nothing added: after a
|
|
43
|
+
* separator (`_`, `-`, `.` and the like), at a change from lower to upper case ("floorPlan"), and between letters
|
|
44
|
+
* and digits ("IMG20261014"). Joined, the pieces are the name.
|
|
45
|
+
*/
|
|
46
|
+
export function fileNameParts(name) {
|
|
47
|
+
const parts = [];
|
|
48
|
+
let part = '';
|
|
49
|
+
const chars = [...name];
|
|
50
|
+
chars.forEach((char, i) => {
|
|
51
|
+
const before = chars[i - 1] ?? '';
|
|
52
|
+
const split = i > 0 &&
|
|
53
|
+
(SEPARATOR.test(before) ||
|
|
54
|
+
(/\p{Ll}/u.test(before) && /\p{Lu}/u.test(char)) ||
|
|
55
|
+
(/\p{L}/u.test(before) && /\p{N}/u.test(char)) ||
|
|
56
|
+
(/\p{N}/u.test(before) && /\p{L}/u.test(char)));
|
|
57
|
+
if (split && part) {
|
|
58
|
+
parts.push(part);
|
|
59
|
+
part = '';
|
|
60
|
+
}
|
|
61
|
+
part += char;
|
|
62
|
+
});
|
|
63
|
+
if (part)
|
|
64
|
+
parts.push(part);
|
|
65
|
+
return parts;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=soft-hyphen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soft-hyphen.js","sourceRoot":"","sources":["../../src/lib/soft-hyphen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text to show, and a ref for the element that shows it, so a word too wide for its line breaks with a hyphen,
|
|
3
|
+
* never between two letters without one. With `overflow-wrap: normal`, a word the browser can't hyphenate runs out
|
|
4
|
+
* of the element; this reads its layout and gives each such word soft hyphens at every place with three letters
|
|
5
|
+
* either side, so the browser breaks it, with a hyphen, at the last place that fits.
|
|
6
|
+
*
|
|
7
|
+
* Give the ref to an `inline-block max-w-full` element holding just this text: it is as wide as its text up to its
|
|
8
|
+
* line, so a ResizeObserver sees the text widen (a selected row turning bold, text spacing) as well as the line
|
|
9
|
+
* narrow. A callback ref rather than an effect, because a collection such as React Aria's Tree renders an item once
|
|
10
|
+
* to build itself and again into the page: the work has to happen when the real element arrives. It only reads the
|
|
11
|
+
* layout, so a long list costs no extra layout per row.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useHyphenated(text: string): [string, (el: HTMLElement | null) => (() => void) | void];
|
|
14
|
+
/**
|
|
15
|
+
* A file name's pieces to show (fileNameParts), and a ref for the `inline-block max-w-full` element that shows them
|
|
16
|
+
* with a `<wbr>` between each: the name breaks where it divides itself, with nothing added. A piece still wider than
|
|
17
|
+
* the line gets soft hyphens at every place with three letters either side, for the browser to break at the last
|
|
18
|
+
* that fits; the element's `hyphenate-character` marks that break as something other than the name's own hyphen.
|
|
19
|
+
* Read when the element arrives and whenever it resizes, as useHyphenated is.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useFileNameParts(name: string): [string[], (el: HTMLElement | null) => (() => void) | void];
|
|
22
|
+
//# sourceMappingURL=use-hyphenated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-hyphenated.d.ts","sourceRoot":"","sources":["../../src/lib/use-hyphenated.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GACX,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAyB3D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAsB7D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { fileNameParts, hyphenateEverywhere, hyphenateTooWide, withoutSoftHyphens, } from './soft-hyphen.js';
|
|
4
|
+
/**
|
|
5
|
+
* The text to show, and a ref for the element that shows it, so a word too wide for its line breaks with a hyphen,
|
|
6
|
+
* never between two letters without one. With `overflow-wrap: normal`, a word the browser can't hyphenate runs out
|
|
7
|
+
* of the element; this reads its layout and gives each such word soft hyphens at every place with three letters
|
|
8
|
+
* either side, so the browser breaks it, with a hyphen, at the last place that fits.
|
|
9
|
+
*
|
|
10
|
+
* Give the ref to an `inline-block max-w-full` element holding just this text: it is as wide as its text up to its
|
|
11
|
+
* line, so a ResizeObserver sees the text widen (a selected row turning bold, text spacing) as well as the line
|
|
12
|
+
* narrow. A callback ref rather than an effect, because a collection such as React Aria's Tree renders an item once
|
|
13
|
+
* to build itself and again into the page: the work has to happen when the real element arrives. It only reads the
|
|
14
|
+
* layout, so a long list costs no extra layout per row.
|
|
15
|
+
*/
|
|
16
|
+
export function useHyphenated(text) {
|
|
17
|
+
const [shown, setShown] = useState(text);
|
|
18
|
+
// A new text from the product starts again; the same text with our soft hyphens added is kept.
|
|
19
|
+
if (shown !== text && withoutSoftHyphens(shown) !== withoutSoftHyphens(text))
|
|
20
|
+
setShown(text);
|
|
21
|
+
const ref = useCallback((el) => {
|
|
22
|
+
if (!el)
|
|
23
|
+
return;
|
|
24
|
+
const check = () => {
|
|
25
|
+
const node = el.firstChild;
|
|
26
|
+
if (!node || node.nodeType !== Node.TEXT_NODE)
|
|
27
|
+
return;
|
|
28
|
+
if (el.scrollWidth <= el.clientWidth + 0.5)
|
|
29
|
+
return;
|
|
30
|
+
const data = node.data;
|
|
31
|
+
const range = document.createRange();
|
|
32
|
+
const next = hyphenateTooWide(data, el.clientWidth, (word, start) => {
|
|
33
|
+
range.setStart(node, start);
|
|
34
|
+
range.setEnd(node, start + word.length);
|
|
35
|
+
return range.getBoundingClientRect().width;
|
|
36
|
+
});
|
|
37
|
+
if (next !== data)
|
|
38
|
+
setShown(next);
|
|
39
|
+
};
|
|
40
|
+
check();
|
|
41
|
+
const observer = new ResizeObserver(check);
|
|
42
|
+
observer.observe(el);
|
|
43
|
+
return () => observer.disconnect();
|
|
44
|
+
}, []);
|
|
45
|
+
return [shown, ref];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A file name's pieces to show (fileNameParts), and a ref for the `inline-block max-w-full` element that shows them
|
|
49
|
+
* with a `<wbr>` between each: the name breaks where it divides itself, with nothing added. A piece still wider than
|
|
50
|
+
* the line gets soft hyphens at every place with three letters either side, for the browser to break at the last
|
|
51
|
+
* that fits; the element's `hyphenate-character` marks that break as something other than the name's own hyphen.
|
|
52
|
+
* Read when the element arrives and whenever it resizes, as useHyphenated is.
|
|
53
|
+
*/
|
|
54
|
+
export function useFileNameParts(name) {
|
|
55
|
+
const [parts, setParts] = useState(() => fileNameParts(name));
|
|
56
|
+
if (withoutSoftHyphens(parts.join('')) !== name)
|
|
57
|
+
setParts(fileNameParts(name));
|
|
58
|
+
const ref = useCallback((el) => {
|
|
59
|
+
if (!el)
|
|
60
|
+
return;
|
|
61
|
+
const check = () => {
|
|
62
|
+
if (el.scrollWidth <= el.clientWidth + 0.5)
|
|
63
|
+
return;
|
|
64
|
+
const range = document.createRange();
|
|
65
|
+
const nodes = [...el.childNodes].filter((n) => n.nodeType === Node.TEXT_NODE);
|
|
66
|
+
const wide = nodes.map((node) => {
|
|
67
|
+
range.selectNodeContents(node);
|
|
68
|
+
return range.getBoundingClientRect().width > el.clientWidth + 0.5;
|
|
69
|
+
});
|
|
70
|
+
if (!wide.some(Boolean))
|
|
71
|
+
return;
|
|
72
|
+
setParts((was) => was.map((part, i) => (wide[i] ? hyphenateEverywhere(part) : part)));
|
|
73
|
+
};
|
|
74
|
+
check();
|
|
75
|
+
const observer = new ResizeObserver(check);
|
|
76
|
+
observer.observe(el);
|
|
77
|
+
return () => observer.disconnect();
|
|
78
|
+
}, []);
|
|
79
|
+
return [parts, ref];
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=use-hyphenated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-hyphenated.js","sourceRoot":"","sources":["../../src/lib/use-hyphenated.ts"],"names":[],"mappings":"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"}
|
package/dist/shell/rail.d.ts
CHANGED
|
@@ -64,7 +64,11 @@ export type RailItemProps = {
|
|
|
64
64
|
onPress?: () => void;
|
|
65
65
|
/** A component, an element, or a drawing of the product's own (a progress ring) that takes `className`. */
|
|
66
66
|
icon: IconProp;
|
|
67
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* The name, always shown under the icon: whole words on two lines at most, shrunk to 11.5px at the least, and a
|
|
69
|
+
* word that still doesn't fit broken with a visible hyphen. A soft hyphen (U+00AD) in it marks where a long word
|
|
70
|
+
* may break; it's left out of the item's accessible name.
|
|
71
|
+
*/
|
|
68
72
|
children: string;
|
|
69
73
|
/** Marks the current page: brand-subtle fill, a marker edge, the icon and name in brand at weight 700, and aria-current. */
|
|
70
74
|
isCurrent?: boolean;
|
package/dist/shell/rail.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rail.d.ts","sourceRoot":"","sources":["../../src/shell/rail.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"rail.d.ts","sourceRoot":"","sources":["../../src/shell/rail.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAKf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAA4C,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElG,gHAAgH;AAChH,MAAM,WAAW,UAAU;IACzB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,UAAU,EAAE,UAMxB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,SAAS,+BA4CrE;AAED,6GAA6G;AAC7G,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAY9F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAY/F;AAED,2GAA2G;AAC3G,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAY9F;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAcA;AAED,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAE5F;AAsDD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,2GAA2G;IAC3G,IAAI,EAAE,QAAQ,CAAC;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG,gBAAgB,CAAC;AAErB,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,MAAM,EACN,UAAU,EACV,MAAM,EACN,WAAW,GACZ,EAAE,aAAa,+BAyEf"}
|
package/dist/shell/rail.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useId } from 'react';
|
|
3
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState, } from 'react';
|
|
4
4
|
import { Link } from 'react-aria-components';
|
|
5
5
|
import { cn } from '../lib/cn.js';
|
|
6
6
|
import { useLabels } from '../lib/locale.js';
|
|
7
|
+
import { fitRailName, plainName } from '../lib/rail-name.js';
|
|
7
8
|
import { Sheet } from '../ui/sheet.js';
|
|
8
9
|
import { useShell } from './dashboard-shell.js';
|
|
9
10
|
import { NavDetail, NavIcon, warnUnlabelledStatus } from './nav-status.js';
|
|
@@ -64,9 +65,61 @@ export function RailGroup({ label, children, className, }) {
|
|
|
64
65
|
export function RailList({ children, className }) {
|
|
65
66
|
return _jsx("ul", { className: cn('flex flex-col gap-(--nav-item-gap)', className), children: children });
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Fits a tile's name (lib/rail-name.ts) by laying it out in a hidden copy of its box, in bold so an item keeps its
|
|
70
|
+
* shape when it becomes the current page. Again whenever the box or the name's own width changes (the rail's
|
|
71
|
+
* width, zoom, text spacing), and when the fonts arrive. In the phone's rows, nothing: a row has the room.
|
|
72
|
+
*/
|
|
73
|
+
function useFittedName(box, name) {
|
|
74
|
+
const [fitted, setFitted] = useState();
|
|
75
|
+
useLayoutEffect(() => {
|
|
76
|
+
const el = box.current;
|
|
77
|
+
const tile = el?.parentElement;
|
|
78
|
+
if (!el || !tile)
|
|
79
|
+
return;
|
|
80
|
+
const fit = () => {
|
|
81
|
+
if (el.closest('[data-layout="rows"]') || !el.clientWidth) {
|
|
82
|
+
setFitted(undefined);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const probe = document.createElement('span');
|
|
86
|
+
probe.className = el.className;
|
|
87
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
88
|
+
// Absolute inside the tile, which is positioned, so it can't scroll anything.
|
|
89
|
+
probe.style.cssText = `position:absolute;visibility:hidden;inset-inline-start:0;top:0;width:${el.clientWidth}px;font-weight:700;overflow-wrap:normal`;
|
|
90
|
+
tile.append(probe);
|
|
91
|
+
const lineHeight = parseFloat(getComputedStyle(probe).lineHeight) || 16;
|
|
92
|
+
const measure = (size, text, mode) => {
|
|
93
|
+
probe.style.fontSize = `${size}px`;
|
|
94
|
+
probe.style.hyphens = mode === 'whole' ? 'manual' : 'auto';
|
|
95
|
+
probe.textContent = mode === 'whole' ? plainName(text) : text;
|
|
96
|
+
return {
|
|
97
|
+
overflows: probe.scrollWidth > probe.clientWidth + 0.5,
|
|
98
|
+
lines: Math.round(probe.scrollHeight / lineHeight),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const next = fitRailName(name, measure);
|
|
102
|
+
probe.remove();
|
|
103
|
+
setFitted((was) => (was?.size === next.size && was.text === next.text ? was : next));
|
|
104
|
+
};
|
|
105
|
+
fit();
|
|
106
|
+
const observer = new ResizeObserver(fit);
|
|
107
|
+
observer.observe(el);
|
|
108
|
+
if (el.firstElementChild)
|
|
109
|
+
observer.observe(el.firstElementChild);
|
|
110
|
+
document.fonts?.addEventListener('loadingdone', fit);
|
|
111
|
+
return () => {
|
|
112
|
+
observer.disconnect();
|
|
113
|
+
document.fonts?.removeEventListener('loadingdone', fit);
|
|
114
|
+
};
|
|
115
|
+
}, [box, name]);
|
|
116
|
+
return fitted;
|
|
117
|
+
}
|
|
67
118
|
export function RailItem({ href, onPress, icon, children, isCurrent, detail, detailTone, status, statusLabel, }) {
|
|
68
119
|
const t = useLabels(['sidebar', 'rail'], railLabels, undefined);
|
|
69
120
|
warnUnlabelledStatus('RailItem', status, statusLabel);
|
|
121
|
+
const nameBox = useRef(null);
|
|
122
|
+
const fitted = useFittedName(nameBox, children);
|
|
70
123
|
return (_jsx("li", { children: _jsx(Link, { href: href, onPress: onPress, "aria-current": isCurrent ? 'page' : undefined, className: cn(
|
|
71
124
|
// Layer 1: the row is the target, the rail's full width and at least --target-min tall.
|
|
72
125
|
'group/item relative flex min-h-target w-full cursor-pointer items-center text-sidebar-foreground no-underline outline-none'), children: _jsxs("span", { className: cn('relative flex w-full min-w-0 flex-col items-center gap-1 rounded-md px-0.5 py-2', 'group-data-hovered/item:bg-sidebar-accent group-data-hovered/item:text-sidebar-accent-foreground group-data-focus-visible/item:focus-ring', 'group-aria-[current=page]/item:bg-brand-subtle group-aria-[current=page]/item:text-foreground',
|
|
@@ -80,7 +133,7 @@ export function RailItem({ href, onPress, icon, children, isCurrent, detail, det
|
|
|
80
133
|
// In the phone's Sheet, a row: the icon beside the name, as the Sidebar's are.
|
|
81
134
|
'group-data-[layout=rows]/rail:min-h-nav-item group-data-[layout=rows]/rail:flex-row group-data-[layout=rows]/rail:gap-3 group-data-[layout=rows]/rail:px-3 group-data-[layout=rows]/rail:py-0',
|
|
82
135
|
// The status badge's cut-out follows the ground: the rail, the hovered tile, the current one.
|
|
83
|
-
'[--nav-cut:var(--sidebar)] group-data-hovered/item:[--nav-cut:var(--sidebar-accent)] group-aria-[current=page]/item:[--nav-cut:var(--brand-subtle)] group-data-hovered/item:group-aria-[current=page]/item:[--nav-cut:var(--brand-subtle)]'), children: [_jsx(NavIcon, { icon: icon, status: status, className: "group-aria-[current=page]/item:text-brand" }),
|
|
136
|
+
'[--nav-cut:var(--sidebar)] group-data-hovered/item:[--nav-cut:var(--sidebar-accent)] group-aria-[current=page]/item:[--nav-cut:var(--brand-subtle)] group-data-hovered/item:group-aria-[current=page]/item:[--nav-cut:var(--brand-subtle)]'), children: [_jsx(NavIcon, { icon: icon, status: status, className: "group-aria-[current=page]/item:text-brand" }), _jsx("span", { "aria-hidden": "true", ref: nameBox, "data-name": "", className: cn('block w-full min-w-0 text-center text-[13px] leading-4 font-medium hyphens-auto', 'group-aria-[current=page]/item:font-bold group-aria-[current=page]/item:text-brand', 'group-data-[layout=rows]/rail:flex-1 group-data-[layout=rows]/rail:text-start group-data-[layout=rows]/rail:text-sm group-data-[layout=rows]/rail:leading-5'), style: fitted && fitted.size !== 13 ? { fontSize: `${fitted.size}px` } : undefined, children: _jsx("span", { className: "inline-block max-w-full", children: fitted?.text ?? children }) }), _jsxs("span", { className: "sr-only", children: [plainName(children), detail && t(detailTone === 'warning' ? 'detailWarning' : 'detail', { detail }), statusLabel && t('status', { status: statusLabel })] }), detail && (_jsx(NavDetail, { detail: detail, tone: detailTone,
|
|
84
137
|
// Under the name in a tile, wrapping rather than running out of it; after the name in a row.
|
|
85
138
|
className: "max-w-full flex-wrap justify-center text-center whitespace-normal [overflow-wrap:anywhere] group-data-[layout=rows]/rail:whitespace-nowrap" }))] }) }) }));
|
|
86
139
|
}
|
package/dist/shell/rail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/shell/rail.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,
|
|
1
|
+
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/shell/rail.tsx"],"names":[],"mappings":"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"}
|
package/dist/styles/theme.css
CHANGED
|
@@ -685,14 +685,15 @@
|
|
|
685
685
|
display: inline-flex;
|
|
686
686
|
align-items: center;
|
|
687
687
|
gap: 6px;
|
|
688
|
-
/* In the rail, names wrap
|
|
688
|
+
/* In the rail, names wrap inside the tile (1.4.12), and a word too wide for it breaks with a hyphen: the
|
|
689
|
+
browser's, or a soft hyphen the Tab adds (useHyphenated). Never overflow-wrap: anywhere, which breaks between
|
|
690
|
+
two letters with no hyphen: Chrome and Firefox don't hyphenate a capitalised English word. */
|
|
689
691
|
[role='tablist'][data-orientation='vertical'] & {
|
|
690
692
|
display: block;
|
|
691
693
|
text-align: center;
|
|
692
694
|
font-size: 13px;
|
|
693
695
|
line-height: 16px;
|
|
694
696
|
hyphens: auto;
|
|
695
|
-
overflow-wrap: anywhere;
|
|
696
697
|
}
|
|
697
698
|
}
|
|
698
699
|
/* The name, over a hidden bold copy that holds the bold width from the start. */
|
|
@@ -701,6 +702,14 @@
|
|
|
701
702
|
& > * {
|
|
702
703
|
grid-area: 1 / 1;
|
|
703
704
|
}
|
|
705
|
+
/* In the rail, no wider than the tile: a word too wide then overflows its copy, where the Tab sees it and gives it
|
|
706
|
+
soft hyphens, rather than widening the grid past the tile. */
|
|
707
|
+
[role='tablist'][data-orientation='vertical'] & {
|
|
708
|
+
max-width: 100%;
|
|
709
|
+
}
|
|
710
|
+
[role='tablist'][data-orientation='vertical'] & > * {
|
|
711
|
+
min-width: 0;
|
|
712
|
+
}
|
|
704
713
|
& > :last-child {
|
|
705
714
|
font-weight: 700;
|
|
706
715
|
visibility: hidden;
|