@charcuterie/ui 3.33.0 → 4.0.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 +30 -0
- package/dist/Board/Board.d.ts.map +1 -1
- package/dist/Board/Board.js +2 -2
- package/dist/Board/Board.js.map +1 -1
- package/dist/Main/Main.d.ts +18 -19
- package/dist/Main/Main.d.ts.map +1 -1
- package/dist/Main/Main.js +21 -10
- package/dist/Main/Main.js.map +1 -1
- package/dist/Main/ScrollMemoryProvider.d.ts +38 -0
- package/dist/Main/ScrollMemoryProvider.d.ts.map +1 -0
- package/dist/Main/ScrollMemoryProvider.js +30 -0
- package/dist/Main/ScrollMemoryProvider.js.map +1 -0
- package/dist/Main/scrollMemory.d.ts +54 -28
- package/dist/Main/scrollMemory.d.ts.map +1 -1
- package/dist/Main/scrollMemory.js +81 -40
- package/dist/Main/scrollMemory.js.map +1 -1
- package/dist/Main/scrollMemoryContext.d.ts +18 -0
- package/dist/Main/scrollMemoryContext.d.ts.map +1 -0
- package/dist/Main/scrollMemoryContext.js +18 -0
- package/dist/Main/scrollMemoryContext.js.map +1 -0
- package/dist/Main/useScrollMemory.d.ts +20 -18
- package/dist/Main/useScrollMemory.d.ts.map +1 -1
- package/dist/Main/useScrollMemory.js +69 -42
- package/dist/Main/useScrollMemory.js.map +1 -1
- package/dist/RangeSlider/RangeSlider.d.ts +117 -0
- package/dist/RangeSlider/RangeSlider.d.ts.map +1 -0
- package/dist/RangeSlider/RangeSlider.js +242 -0
- package/dist/RangeSlider/RangeSlider.js.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts +102 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.js +116 -0
- package/dist/RangeSlider/rangeSliderValue.js.map +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +12 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -3
- package/dist/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts +2 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.js +7 -2
- package/dist/SegmentedControl/SegmentedOption.js.map +1 -1
- package/dist/Slider/Slider.d.ts +6 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +8 -22
- package/dist/Slider/Slider.js.map +1 -1
- package/dist/TimecodeInput/TimecodeInput.d.ts +88 -0
- package/dist/TimecodeInput/TimecodeInput.d.ts.map +1 -0
- package/dist/TimecodeInput/TimecodeInput.js +258 -0
- package/dist/TimecodeInput/TimecodeInput.js.map +1 -0
- package/dist/TimecodeInput/timecode.d.ts +132 -0
- package/dist/TimecodeInput/timecode.d.ts.map +1 -0
- package/dist/TimecodeInput/timecode.js +246 -0
- package/dist/TimecodeInput/timecode.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/reactRouter/ReactRouterAdapter.d.ts +43 -0
- package/dist/reactRouter/ReactRouterAdapter.d.ts.map +1 -0
- package/dist/reactRouter/ReactRouterAdapter.js +50 -0
- package/dist/reactRouter/ReactRouterAdapter.js.map +1 -0
- package/dist/reactRouter/index.d.ts +14 -8
- package/dist/reactRouter/index.d.ts.map +1 -1
- package/dist/reactRouter/index.js +13 -8
- package/dist/reactRouter/index.js.map +1 -1
- package/dist/sliderStyles.d.ts +39 -0
- package/dist/sliderStyles.d.ts.map +1 -0
- package/dist/sliderStyles.js +50 -0
- package/dist/sliderStyles.js.map +1 -0
- package/package.json +1 -1
- package/src/Board/Board.test.tsx +8 -0
- package/src/Board/Board.tsx +7 -1
- package/src/Main/Main.mdx +35 -7
- package/src/Main/Main.stories.tsx +129 -64
- package/src/Main/Main.test.tsx +58 -0
- package/src/Main/Main.tsx +20 -20
- package/src/Main/ScrollMemoryProvider.tsx +48 -0
- package/src/Main/scrollMemory.test.ts +70 -16
- package/src/Main/scrollMemory.ts +98 -40
- package/src/Main/scrollMemoryContext.ts +21 -0
- package/src/Main/useScrollMemory.ts +83 -43
- package/src/RangeSlider/RangeSlider.mdx +85 -0
- package/src/RangeSlider/RangeSlider.stories.tsx +346 -0
- package/src/RangeSlider/RangeSlider.test.tsx +452 -0
- package/src/RangeSlider/RangeSlider.tsx +595 -0
- package/src/RangeSlider/rangeSliderValue.test.ts +217 -0
- package/src/RangeSlider/rangeSliderValue.ts +185 -0
- package/src/RouterLink/RouterLink.mdx +53 -16
- package/src/SegmentedControl/SegmentedControl.stories.tsx +50 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +36 -1
- package/src/SegmentedControl/SegmentedControl.tsx +14 -0
- package/src/SegmentedControl/SegmentedOption.tsx +7 -0
- package/src/Slider/Slider.tsx +23 -29
- package/src/TimecodeInput/TimecodeInput.mdx +155 -0
- package/src/TimecodeInput/TimecodeInput.stories.tsx +304 -0
- package/src/TimecodeInput/TimecodeInput.test.tsx +279 -0
- package/src/TimecodeInput/TimecodeInput.tsx +493 -0
- package/src/TimecodeInput/timecode.test.ts +236 -0
- package/src/TimecodeInput/timecode.ts +391 -0
- package/src/index.ts +21 -0
- package/src/reactRouter/ReactRouterAdapter.stories.tsx +120 -0
- package/src/reactRouter/ReactRouterAdapter.test.tsx +92 -0
- package/src/reactRouter/ReactRouterAdapter.tsx +66 -0
- package/src/reactRouter/index.ts +14 -8
- package/src/sliderStyles.ts +69 -0
- package/src/sourceRules.test.ts +29 -3
package/README.md
CHANGED
|
@@ -96,6 +96,36 @@ it — the class really was in the DOM, tsc never reads the CSS, and unstyled ma
|
|
|
96
96
|
axe
|
|
97
97
|
([decision](../../docs/decisions/2026-09-01-a-tile-that-acts-is-its-own-component-and-shares-only-the-box.md)).
|
|
98
98
|
|
|
99
|
+
**`TimecodeInput` is the first field the fleet could not write down twice the same way.**
|
|
100
|
+
QueuePilot is adding "play only this section of a video", and the *printing* half of a
|
|
101
|
+
timecode already exists five times — queuepilot's `clock` and `toClock`, castkit's
|
|
102
|
+
`formatTime`, and mux-magic's two modal formatters — with three different answers to whether
|
|
103
|
+
the hour is shown and two to whether the minute is padded, so the same position renders
|
|
104
|
+
differently in two panels of one app. The *reading* half exists nowhere. It is a text field
|
|
105
|
+
rather than an `<input type="time">` for the reason `Select` is deprecated and `Slider` is a
|
|
106
|
+
`div`: the platform widget is OS-painted, and it cannot carry a millisecond anyway. The
|
|
107
|
+
grammar is ordered, anchored and total — `90` is ninety seconds, `90:00` is ninety minutes,
|
|
108
|
+
and `1:90` is refused by name rather than carried into `2:30`, which is the `1:60` trap
|
|
109
|
+
mux-magic wrote down in 2023. `isRange` edits a section whose **two ends are independently
|
|
110
|
+
optional**; an inverted section swaps, and a zero-length one is refused, because a section
|
|
111
|
+
with no length plays nothing
|
|
112
|
+
([decision](../../docs/decisions/2026-09-01-a-timecode-is-milliseconds-and-a-section-is-a-mode.md)).
|
|
113
|
+
**`RangeSlider` is the two-thumb range**, and it exists because
|
|
114
|
+
[the Slider record](../../docs/decisions/2026-08-22-the-slider-is-a-div-with-role-slider-not-an-input-range.md)
|
|
115
|
+
said one would be a different widget: QueuePilot plays *only a section* of a video, so a
|
|
116
|
+
user drags two handles over a film's runtime and reads the result beside a `TimecodeInput`.
|
|
117
|
+
`Slider` puts `role="slider"`, the tab stop and the pointer target on the **track**, and two
|
|
118
|
+
values cannot live on one accessible object — so here the role moves to the thumbs, the
|
|
119
|
+
track becomes a `role="group"` carrying the label, and one tab stop becomes two. That is why
|
|
120
|
+
it is a component rather than an `isRange` mode: both halves of the test that made
|
|
121
|
+
`Combobox`'s `isMultiple` and `DatePicker`'s `isRange` modes — *the role does not change,
|
|
122
|
+
and the shared part is everything* — are false here. The thumbs **clamp** rather than swap,
|
|
123
|
+
so the handle under the pointer keeps its identity and each thumb reports the other as its
|
|
124
|
+
own `aria-valuemin`/`aria-valuemax`. What is genuinely shared is the arithmetic
|
|
125
|
+
(`Slider/sliderValue.ts`, called rather than copied) and the bar (`sliderStyles.ts`, the
|
|
126
|
+
same extraction `tileStyles.ts` is), compared as **computed** styles in the test
|
|
127
|
+
([decision](../../docs/decisions/2026-09-01-a-two-thumb-range-is-its-own-component-and-shares-the-bar.md)).
|
|
128
|
+
|
|
99
129
|
**The unified app shell adds four**: `Shell`, `Header`, `Rail`, `Main` — the fleet's largest
|
|
100
130
|
single duplication, where ten of twelve UI repos hand-roll the page chrome and three of them
|
|
101
131
|
have a file named `AppShell.tsx` (two of those headers are a *byte-identical* class string,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.d.ts","sourceRoot":"","sources":["../../src/Board/Board.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AASpD,MAAM,MAAM,SAAS,GAAG;IACtB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IAC/B,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACnC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,eAAO,MAAM,KAAK,GAAI,yFASnB,UAAU,KAAG,
|
|
1
|
+
{"version":3,"file":"Board.d.ts","sourceRoot":"","sources":["../../src/Board/Board.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AASpD,MAAM,MAAM,SAAS,GAAG;IACtB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IAC/B,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACnC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,eAAO,MAAM,KAAK,GAAI,yFASnB,UAAU,KAAG,SAmOf,CAAA"}
|
package/dist/Board/Board.js
CHANGED
|
@@ -144,13 +144,13 @@ export const Board = ({ className, headingLevel = 3, label, laneLayout = "column
|
|
|
144
144
|
*/
|
|
145
145
|
className: toClassName("@container/board", className), children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(SegmentedControl, { className: toClassName("max-w-full flex-wrap", laneLayout === "rows"
|
|
146
146
|
? "hidden"
|
|
147
|
-
: "cq-lg:hidden"), items: lanes.map((lane) => ({
|
|
147
|
+
: "cq-lg:hidden"), isFullWidth: true, items: lanes.map((lane) => ({
|
|
148
148
|
// Stacked rather than `Todo (6)`, because the count is
|
|
149
149
|
// the reason to look: a lane picker that reads
|
|
150
150
|
// "In Progress (5)" on one line is a long label with a
|
|
151
151
|
// number in it, and at 15rem three of them do not fit
|
|
152
152
|
// on any line at all.
|
|
153
|
-
label: (_jsxs("span", { className: "flex flex-col items-center leading-tight", children: [_jsx("span", { children: lane.label }), _jsx("span", { className: "font-normal text-content-muted text-xs", children: String(lane.itemCount ?? lane.items.length) })] })),
|
|
153
|
+
label: (_jsxs("span", { className: "flex flex-col items-center leading-tight", children: [_jsx("span", { className: "block whitespace-nowrap", children: lane.label }), _jsx("span", { className: "font-normal text-content-muted text-xs", children: String(lane.itemCount ?? lane.items.length) })] })),
|
|
154
154
|
value: lane.key,
|
|
155
155
|
})), label: `${label} lanes`, onChange: (value) => {
|
|
156
156
|
setVisibleLaneKey(value ?? "");
|
package/dist/Board/Board.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Board.js","sourceRoot":"","sources":["../../src/Board/Board.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Board.js","sourceRoot":"","sources":["../../src/Board/Board.tsx"],"names":[],"mappings":"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"}
|
package/dist/Main/Main.d.ts
CHANGED
|
@@ -2,16 +2,6 @@ import { type ComponentPropsWithRef, type ReactNode } from "react";
|
|
|
2
2
|
import { type ContentWidth } from "../Shell/contentWidth.ts";
|
|
3
3
|
export type MainProps = ComponentPropsWithRef<"main"> & {
|
|
4
4
|
contentWidth?: ContentWidth;
|
|
5
|
-
/**
|
|
6
|
-
* The history entry this page belongs to — react-router's
|
|
7
|
-
* `useLocation().key`, and the same value under a different
|
|
8
|
-
* router. Given it, `<main>` remembers where each entry was
|
|
9
|
-
* scrolled to and puts the offset back on Back and Forward.
|
|
10
|
-
*
|
|
11
|
-
* Omit it and nothing is remembered, which is the right default
|
|
12
|
-
* for a page with no history to remember.
|
|
13
|
-
*/
|
|
14
|
-
scrollKey?: string;
|
|
15
5
|
};
|
|
16
6
|
/**
|
|
17
7
|
* The `<main>` landmark and the content column.
|
|
@@ -102,18 +92,27 @@ export type MainProps = ComponentPropsWithRef<"main"> & {
|
|
|
102
92
|
* therefore lands at the top of a list the reader had scrolled
|
|
103
93
|
* half-way down, and no browser setting changes that.
|
|
104
94
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* `
|
|
95
|
+
* So this element remembers it, and **there is no prop for it**.
|
|
96
|
+
* The history entry arrives on `ScrollMemoryContext`, which
|
|
97
|
+
* `ReactRouterAdapter` puts at the app root:
|
|
108
98
|
*
|
|
109
99
|
* ```tsx
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* </
|
|
100
|
+
* <BrowserRouter>
|
|
101
|
+
* <ReactRouterAdapter>
|
|
102
|
+
* <Routes>…</Routes>
|
|
103
|
+
* </ReactRouterAdapter>
|
|
104
|
+
* </BrowserRouter>
|
|
115
105
|
* ```
|
|
116
106
|
*
|
|
107
|
+
* It was a prop for one release — `scrollKey` — and that release is
|
|
108
|
+
* the argument against a prop. One app wired it; the other three
|
|
109
|
+
* carried on losing the reader's place with the library sitting
|
|
110
|
+
* right there supporting it. Render no provider and nothing is
|
|
111
|
+
* remembered, which is what an app with no router should get.
|
|
112
|
+
*
|
|
113
|
+
* `useScrollMemory.ts` holds the mechanism and `scrollMemory.ts`
|
|
114
|
+
* the reasoning.
|
|
115
|
+
*
|
|
117
116
|
* ## Two more things it does, both one line
|
|
118
117
|
*
|
|
119
118
|
* `tabIndex={-1}` so `Shell`'s skip link moves **focus** and not
|
|
@@ -143,5 +142,5 @@ export type MainProps = ComponentPropsWithRef<"main"> & {
|
|
|
143
142
|
* wrap, so a wide one goes in an `overflow-x-auto` wrapper, which
|
|
144
143
|
* `Main.mdx` shows.
|
|
145
144
|
*/
|
|
146
|
-
export declare const Main: ({ children, className, contentWidth, id, ref,
|
|
145
|
+
export declare const Main: ({ children, className, contentWidth, id, ref, ...mainProps }: MainProps) => ReactNode;
|
|
147
146
|
//# sourceMappingURL=Main.d.ts.map
|
package/dist/Main/Main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Main.d.ts","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAGf,MAAM,OAAO,CAAA;AAEd,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"Main.d.ts","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAGf,MAAM,OAAO,CAAA;AAEd,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,0BAA0B,CAAA;AAMjC,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,GAAG;IACtD,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0IG;AACH,eAAO,MAAM,IAAI,GAAI,8DAOlB,SAAS,KAAG,SA0Dd,CAAA"}
|
package/dist/Main/Main.js
CHANGED
|
@@ -4,6 +4,7 @@ import { useContext, } from "react";
|
|
|
4
4
|
import { DEFAULT_CONTENT_WIDTH, toMaxInlineSize, } from "../Shell/contentWidth.js";
|
|
5
5
|
import { ShellContext } from "../Shell/shellContext.js";
|
|
6
6
|
import { toClassName } from "../toClassName.js";
|
|
7
|
+
import { ScrollMemoryContext } from "./scrollMemoryContext.js";
|
|
7
8
|
import { useScrollMemory } from "./useScrollMemory.js";
|
|
8
9
|
/**
|
|
9
10
|
* The `<main>` landmark and the content column.
|
|
@@ -94,18 +95,27 @@ import { useScrollMemory } from "./useScrollMemory.js";
|
|
|
94
95
|
* therefore lands at the top of a list the reader had scrolled
|
|
95
96
|
* half-way down, and no browser setting changes that.
|
|
96
97
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
98
|
+
* So this element remembers it, and **there is no prop for it**.
|
|
99
|
+
* The history entry arrives on `ScrollMemoryContext`, which
|
|
100
|
+
* `ReactRouterAdapter` puts at the app root:
|
|
100
101
|
*
|
|
101
102
|
* ```tsx
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* </
|
|
103
|
+
* <BrowserRouter>
|
|
104
|
+
* <ReactRouterAdapter>
|
|
105
|
+
* <Routes>…</Routes>
|
|
106
|
+
* </ReactRouterAdapter>
|
|
107
|
+
* </BrowserRouter>
|
|
107
108
|
* ```
|
|
108
109
|
*
|
|
110
|
+
* It was a prop for one release — `scrollKey` — and that release is
|
|
111
|
+
* the argument against a prop. One app wired it; the other three
|
|
112
|
+
* carried on losing the reader's place with the library sitting
|
|
113
|
+
* right there supporting it. Render no provider and nothing is
|
|
114
|
+
* remembered, which is what an app with no router should get.
|
|
115
|
+
*
|
|
116
|
+
* `useScrollMemory.ts` holds the mechanism and `scrollMemory.ts`
|
|
117
|
+
* the reasoning.
|
|
118
|
+
*
|
|
109
119
|
* ## Two more things it does, both one line
|
|
110
120
|
*
|
|
111
121
|
* `tabIndex={-1}` so `Shell`'s skip link moves **focus** and not
|
|
@@ -135,9 +145,10 @@ import { useScrollMemory } from "./useScrollMemory.js";
|
|
|
135
145
|
* wrap, so a wide one goes in an `overflow-x-auto` wrapper, which
|
|
136
146
|
* `Main.mdx` shows.
|
|
137
147
|
*/
|
|
138
|
-
export const Main = ({ children, className, contentWidth, id, ref,
|
|
148
|
+
export const Main = ({ children, className, contentWidth, id, ref, ...mainProps }) => {
|
|
139
149
|
const shell = useContext(ShellContext);
|
|
140
|
-
const
|
|
150
|
+
const scrollEntry = useContext(ScrollMemoryContext);
|
|
151
|
+
const setScrollport = useScrollMemory(scrollEntry);
|
|
141
152
|
// Always called, never conditionally: a `Main` outside a `Shell`
|
|
142
153
|
// still needs an id an app can point its own skip link at.
|
|
143
154
|
const fallbackId = useUniqueId();
|
package/dist/Main/Main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Main.js","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEL,SAAS,EACT,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAIL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"Main.js","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEL,SAAS,EACT,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAIL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAMtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0IG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,EAAE,EACF,GAAG,EACH,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IACtC,MAAM,WAAW,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAA;IACnD,MAAM,aAAa,GAAG,eAAe,CAAC,WAAW,CAAC,CAAA;IAElD,iEAAiE;IACjE,2DAA2D;IAC3D,MAAM,UAAU,GAAG,WAAW,EAAE,CAAA;IAEhC,MAAM,aAAa,GAAG,eAAe,CACnC,YAAY;QACV,KAAK,EAAE,YAAY;QACnB,qBAAqB,CACxB,CAAA;IAED,OAAO,CACL,kBACM,SAAS,EACb,SAAS,EAAE,WAAW,CACpB,6GAA6G,EAC7G,SAAS,CACV,EACD,EAAE,EAAE,EAAE,IAAI,KAAK,EAAE,MAAM,IAAI,UAAU;QACrC,0DAA0D;QAC1D,2DAA2D;QAC3D,EAAE;QACF,yDAAyD;QACzD,8DAA8D;QAC9D,qBAAqB;QACrB,GAAG,EACD,GAAG;YACD,CAAC,CAAE,SAAS,CACR,GAAmB,EACnB,aAAa,CACe;YAChC,CAAC,CAAC,aAAa,EAEnB,QAAQ,EAAE,CAAC,CAAC,YAaZ,cACE,SAAS,EAAC,uFAAuF,EACjG,KAAK,EAAE,EAAE,aAAa,EAAE,YAEvB,QAAQ,GACL,GACD,CACR,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ScrollEntry } from "./scrollMemory.ts";
|
|
3
|
+
export type ScrollMemoryProviderProps = {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* The history entry the page is showing, and the path it belongs
|
|
7
|
+
* to. Under react-router both come straight off `useLocation()`.
|
|
8
|
+
*/
|
|
9
|
+
entry: ScrollEntry;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Tell every `Main` below this which history entry it is showing,
|
|
13
|
+
* so Back and Forward return the reader to where they were.
|
|
14
|
+
*
|
|
15
|
+
* An app that uses react-router should not render this directly —
|
|
16
|
+
* `ReactRouterAdapter` in the `./react-router` subpath wires it,
|
|
17
|
+
* along with the link seam, in one component at the root. This is
|
|
18
|
+
* the router-free half, for an app on a different router:
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const [location] = useLocation() // wouter, say
|
|
22
|
+
*
|
|
23
|
+
* <ScrollMemoryProvider
|
|
24
|
+
* entry={{ key: historyKey, path: location }}
|
|
25
|
+
* >
|
|
26
|
+
* <App />
|
|
27
|
+
* </ScrollMemoryProvider>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* **`key` must identify the history entry, not the URL.** Two visits
|
|
31
|
+
* to the same list are two entries and the reader may leave them at
|
|
32
|
+
* different offsets; a key derived from the path would give the
|
|
33
|
+
* second visit the first one's position and could not tell Back from
|
|
34
|
+
* Forward. A router that exposes no such key can pass a counter it
|
|
35
|
+
* increments on every push and restores on every pop.
|
|
36
|
+
*/
|
|
37
|
+
export declare const ScrollMemoryProvider: ({ children, entry, }: ScrollMemoryProviderProps) => ReactNode;
|
|
38
|
+
//# sourceMappingURL=ScrollMemoryProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollMemoryProvider.d.ts","sourceRoot":"","sources":["../../src/Main/ScrollMemoryProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGpD,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,WAAW,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oBAAoB,GAAI,sBAGlC,yBAAyB,KAAG,SAI9B,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ScrollMemoryContext } from "./scrollMemoryContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* Tell every `Main` below this which history entry it is showing,
|
|
5
|
+
* so Back and Forward return the reader to where they were.
|
|
6
|
+
*
|
|
7
|
+
* An app that uses react-router should not render this directly —
|
|
8
|
+
* `ReactRouterAdapter` in the `./react-router` subpath wires it,
|
|
9
|
+
* along with the link seam, in one component at the root. This is
|
|
10
|
+
* the router-free half, for an app on a different router:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const [location] = useLocation() // wouter, say
|
|
14
|
+
*
|
|
15
|
+
* <ScrollMemoryProvider
|
|
16
|
+
* entry={{ key: historyKey, path: location }}
|
|
17
|
+
* >
|
|
18
|
+
* <App />
|
|
19
|
+
* </ScrollMemoryProvider>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* **`key` must identify the history entry, not the URL.** Two visits
|
|
23
|
+
* to the same list are two entries and the reader may leave them at
|
|
24
|
+
* different offsets; a key derived from the path would give the
|
|
25
|
+
* second visit the first one's position and could not tell Back from
|
|
26
|
+
* Forward. A router that exposes no such key can pass a counter it
|
|
27
|
+
* increments on every push and restores on every pop.
|
|
28
|
+
*/
|
|
29
|
+
export const ScrollMemoryProvider = ({ children, entry, }) => (_jsx(ScrollMemoryContext.Provider, { value: entry, children: children }));
|
|
30
|
+
//# sourceMappingURL=ScrollMemoryProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollMemoryProvider.js","sourceRoot":"","sources":["../../src/Main/ScrollMemoryProvider.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAW9D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EACnC,QAAQ,EACR,KAAK,GACqB,EAAa,EAAE,CAAC,CAC1C,KAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACvC,QAAQ,GACoB,CAChC,CAAA"}
|
|
@@ -1,40 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What the browser will not remember for you
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* does **not** do it for an arbitrary element with
|
|
14
|
-
* `overflow-y: auto`, and it does not do it at all for a
|
|
15
|
-
* same-document `pushState` navigation, which is every link press
|
|
16
|
-
* in a single-page app. So a page that scrolls its own `<main>`
|
|
17
|
-
* loses the reader's place on Back, and no browser setting brings
|
|
18
|
-
* it back:
|
|
2
|
+
* What the browser will not remember for you, and the one thing it
|
|
3
|
+
* would never have got wrong.
|
|
4
|
+
*
|
|
5
|
+
* `Shell` gives `<main>` the page's only vertical scrollport, so the
|
|
6
|
+
* header and the rails stay put while the content moves — that is
|
|
7
|
+
* what `Shell.test.tsx` asserts. It costs exactly one thing, and
|
|
8
|
+
* this module is it. A browser remembers **the document scroller's**
|
|
9
|
+
* offset per history entry and puts it back on Back. It does not do
|
|
10
|
+
* that for an arbitrary element with `overflow-y: auto`, and it does
|
|
11
|
+
* not do it at all for a same-document `pushState` navigation, which
|
|
12
|
+
* is every link press in a single-page app:
|
|
19
13
|
*
|
|
20
14
|
* > *"If I scroll, click on an item, then go back, it resets me to
|
|
21
15
|
* > the top. […] It should remember where the scroll position was
|
|
22
16
|
* > previously. Isn't this handled by Chrome already?"*
|
|
23
17
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
18
|
+
* ## An entry remembers its PATH as well as its offset
|
|
19
|
+
*
|
|
20
|
+
* Not decoration — the second field is what keeps this from being a
|
|
21
|
+
* worse bug than the one it fixes. A history entry is not a page. A
|
|
22
|
+
* filter chip, an expanded group, a selected tab: each writes a
|
|
23
|
+
* search param, and **`setSearchParams` navigates**, so each mints a
|
|
24
|
+
* history entry the memory has never seen. Treating an unseen entry
|
|
25
|
+
* as "start at the top" therefore threw the reader to the top of the
|
|
26
|
+
* list every time they opened a group — which is exactly what
|
|
27
|
+
* shipped, and what the path comparison exists to prevent.
|
|
28
|
+
*
|
|
29
|
+
* So the rule is about the PAGE, not the entry:
|
|
30
|
+
*
|
|
31
|
+
* - **An entry we have seen** → its remembered offset. This is the
|
|
32
|
+
* whole feature: Back and Forward land where you left them.
|
|
33
|
+
* - **A new entry on the SAME path** → leave the scrollport alone.
|
|
34
|
+
* A search param changed; the reader did not go anywhere. This is
|
|
35
|
+
* also precisely what the app did before this memory existed, so
|
|
36
|
+
* it can never be a regression.
|
|
37
|
+
* - **A new entry on a DIFFERENT path** → the top. A page you have
|
|
38
|
+
* not seen starts at its beginning, which is what a browser does
|
|
39
|
+
* for a document.
|
|
40
|
+
*
|
|
41
|
+
* ## The offsets are in memory, never `sessionStorage`
|
|
42
|
+
*
|
|
43
|
+
* A history key survives a reload; the DOM it described does not.
|
|
44
|
+
* The app re-fetches, re-measures and often re-orders, so a pixel
|
|
45
|
+
* offset from before the reload points at a different row. Restoring
|
|
46
|
+
* a stale number is worse than restoring nothing, because it looks
|
|
47
|
+
* deliberate.
|
|
30
48
|
*/
|
|
31
|
-
export
|
|
49
|
+
export type ScrollEntry = {
|
|
50
|
+
/** The history entry — react-router's `useLocation().key`. */
|
|
51
|
+
key: string;
|
|
52
|
+
/** The page it belongs to — `useLocation().pathname`. */
|
|
53
|
+
path: string;
|
|
54
|
+
};
|
|
55
|
+
export declare const rememberScrollOffset: (entry: ScrollEntry, offset: number) => void;
|
|
32
56
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* the
|
|
57
|
+
* Where the scrollport should go for this entry, or `null` for
|
|
58
|
+
* **leave it alone** — which is a third answer rather than a missing
|
|
59
|
+
* one, and the difference between a search param and a page.
|
|
36
60
|
*/
|
|
37
|
-
export declare const
|
|
61
|
+
export declare const resolveScrollOffset: (entry: ScrollEntry) => number | null;
|
|
62
|
+
/** Called by the hook once it owns the scrollport for this entry. */
|
|
63
|
+
export declare const enterScrollEntry: (entry: ScrollEntry) => void;
|
|
38
64
|
/** Test-only. Module state outlives a mount; a test must not. */
|
|
39
65
|
export declare const forgetScrollOffsets: () => void;
|
|
40
66
|
//# sourceMappingURL=scrollMemory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrollMemory.d.ts","sourceRoot":"","sources":["../../src/Main/scrollMemory.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scrollMemory.d.ts","sourceRoot":"","sources":["../../src/Main/scrollMemory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAUH,MAAM,MAAM,WAAW,GAAG;IACxB,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAA;IACX,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAkBD,eAAO,MAAM,oBAAoB,GAC/B,OAAO,WAAW,EAClB,QAAQ,MAAM,SAkBf,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,WAAW,kBAYrD,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,GAAI,OAAO,WAAW,SAElD,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,YAG/B,CAAA"}
|
|
@@ -1,63 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What the browser will not remember for you
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* does **not** do it for an arbitrary element with
|
|
14
|
-
* `overflow-y: auto`, and it does not do it at all for a
|
|
15
|
-
* same-document `pushState` navigation, which is every link press
|
|
16
|
-
* in a single-page app. So a page that scrolls its own `<main>`
|
|
17
|
-
* loses the reader's place on Back, and no browser setting brings
|
|
18
|
-
* it back:
|
|
2
|
+
* What the browser will not remember for you, and the one thing it
|
|
3
|
+
* would never have got wrong.
|
|
4
|
+
*
|
|
5
|
+
* `Shell` gives `<main>` the page's only vertical scrollport, so the
|
|
6
|
+
* header and the rails stay put while the content moves — that is
|
|
7
|
+
* what `Shell.test.tsx` asserts. It costs exactly one thing, and
|
|
8
|
+
* this module is it. A browser remembers **the document scroller's**
|
|
9
|
+
* offset per history entry and puts it back on Back. It does not do
|
|
10
|
+
* that for an arbitrary element with `overflow-y: auto`, and it does
|
|
11
|
+
* not do it at all for a same-document `pushState` navigation, which
|
|
12
|
+
* is every link press in a single-page app:
|
|
19
13
|
*
|
|
20
14
|
* > *"If I scroll, click on an item, then go back, it resets me to
|
|
21
15
|
* > the top. […] It should remember where the scroll position was
|
|
22
16
|
* > previously. Isn't this handled by Chrome already?"*
|
|
23
17
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
18
|
+
* ## An entry remembers its PATH as well as its offset
|
|
19
|
+
*
|
|
20
|
+
* Not decoration — the second field is what keeps this from being a
|
|
21
|
+
* worse bug than the one it fixes. A history entry is not a page. A
|
|
22
|
+
* filter chip, an expanded group, a selected tab: each writes a
|
|
23
|
+
* search param, and **`setSearchParams` navigates**, so each mints a
|
|
24
|
+
* history entry the memory has never seen. Treating an unseen entry
|
|
25
|
+
* as "start at the top" therefore threw the reader to the top of the
|
|
26
|
+
* list every time they opened a group — which is exactly what
|
|
27
|
+
* shipped, and what the path comparison exists to prevent.
|
|
28
|
+
*
|
|
29
|
+
* So the rule is about the PAGE, not the entry:
|
|
30
|
+
*
|
|
31
|
+
* - **An entry we have seen** → its remembered offset. This is the
|
|
32
|
+
* whole feature: Back and Forward land where you left them.
|
|
33
|
+
* - **A new entry on the SAME path** → leave the scrollport alone.
|
|
34
|
+
* A search param changed; the reader did not go anywhere. This is
|
|
35
|
+
* also precisely what the app did before this memory existed, so
|
|
36
|
+
* it can never be a regression.
|
|
37
|
+
* - **A new entry on a DIFFERENT path** → the top. A page you have
|
|
38
|
+
* not seen starts at its beginning, which is what a browser does
|
|
39
|
+
* for a document.
|
|
40
|
+
*
|
|
41
|
+
* ## The offsets are in memory, never `sessionStorage`
|
|
42
|
+
*
|
|
43
|
+
* A history key survives a reload; the DOM it described does not.
|
|
44
|
+
* The app re-fetches, re-measures and often re-orders, so a pixel
|
|
45
|
+
* offset from before the reload points at a different row. Restoring
|
|
46
|
+
* a stale number is worse than restoring nothing, because it looks
|
|
47
|
+
* deliberate.
|
|
30
48
|
*/
|
|
31
49
|
/**
|
|
32
50
|
* How many history entries keep an offset. A reader goes back
|
|
33
51
|
* through a handful of screens, never fifty, so this is a ceiling
|
|
34
52
|
* against a long session rather than a working set — the map holds
|
|
35
|
-
* one small
|
|
36
|
-
* written.
|
|
53
|
+
* one small record per entry and evicts the least recently written.
|
|
37
54
|
*/
|
|
38
55
|
const REMEMBERED_ENTRY_LIMIT = 50;
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const rememberedEntries = new Map();
|
|
57
|
+
/**
|
|
58
|
+
* The entry the scrollport is showing. Module state rather than a
|
|
59
|
+
* ref, because the question it answers — *did the reader change page
|
|
60
|
+
* or only a search param?* — is asked by the incoming entry's effect
|
|
61
|
+
* about the OUTGOING one, and by then the outgoing effect has
|
|
62
|
+
* already been cleaned up.
|
|
63
|
+
*/
|
|
64
|
+
let currentEntry = null;
|
|
65
|
+
export const rememberScrollOffset = (entry, offset) => {
|
|
66
|
+
// Delete before set, so insertion order is least-recent-first and
|
|
67
|
+
// the eviction below takes the right one. A plain `set` on an
|
|
68
|
+
// existing key keeps its original position.
|
|
69
|
+
rememberedEntries.delete(entry.key);
|
|
70
|
+
rememberedEntries.set(entry.key, {
|
|
71
|
+
offset,
|
|
72
|
+
path: entry.path,
|
|
73
|
+
});
|
|
74
|
+
for (const oldestKey of rememberedEntries.keys()) {
|
|
75
|
+
if (rememberedEntries.size <= REMEMBERED_ENTRY_LIMIT) {
|
|
48
76
|
break;
|
|
49
77
|
}
|
|
50
|
-
|
|
78
|
+
rememberedEntries.delete(oldestKey);
|
|
51
79
|
}
|
|
52
80
|
};
|
|
53
81
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the
|
|
82
|
+
* Where the scrollport should go for this entry, or `null` for
|
|
83
|
+
* **leave it alone** — which is a third answer rather than a missing
|
|
84
|
+
* one, and the difference between a search param and a page.
|
|
57
85
|
*/
|
|
58
|
-
export const
|
|
86
|
+
export const resolveScrollOffset = (entry) => {
|
|
87
|
+
const remembered = rememberedEntries.get(entry.key);
|
|
88
|
+
if (remembered) {
|
|
89
|
+
return remembered.offset;
|
|
90
|
+
}
|
|
91
|
+
const isSamePage = currentEntry !== null &&
|
|
92
|
+
currentEntry.path === entry.path;
|
|
93
|
+
return isSamePage ? null : 0;
|
|
94
|
+
};
|
|
95
|
+
/** Called by the hook once it owns the scrollport for this entry. */
|
|
96
|
+
export const enterScrollEntry = (entry) => {
|
|
97
|
+
currentEntry = entry;
|
|
98
|
+
};
|
|
59
99
|
/** Test-only. Module state outlives a mount; a test must not. */
|
|
60
100
|
export const forgetScrollOffsets = () => {
|
|
61
|
-
|
|
101
|
+
rememberedEntries.clear();
|
|
102
|
+
currentEntry = null;
|
|
62
103
|
};
|
|
63
104
|
//# sourceMappingURL=scrollMemory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrollMemory.js","sourceRoot":"","sources":["../../src/Main/scrollMemory.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scrollMemory.js","sourceRoot":"","sources":["../../src/Main/scrollMemory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,EAAE,CAAA;AAcjC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAA;AAE5D;;;;;;GAMG;AACH,IAAI,YAAY,GAAuB,IAAI,CAAA;AAE3C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,KAAkB,EAClB,MAAc,EACd,EAAE;IACF,kEAAkE;IAClE,8DAA8D;IAC9D,4CAA4C;IAC5C,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE;QAC/B,MAAM;QACN,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAA;IAEF,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC;QACjD,IAAI,iBAAiB,CAAC,IAAI,IAAI,sBAAsB,EAAE,CAAC;YACrD,MAAK;QACP,CAAC;QAED,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrC,CAAC;AACH,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAkB,EAAE,EAAE;IACxD,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAEnD,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,UAAU,CAAC,MAAM,CAAA;IAC1B,CAAC;IAED,MAAM,UAAU,GACd,YAAY,KAAK,IAAI;QACrB,YAAY,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAA;IAElC,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAkB,EAAE,EAAE;IACrD,YAAY,GAAG,KAAK,CAAA;AACtB,CAAC,CAAA;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,EAAE;IACtC,iBAAiB,CAAC,KAAK,EAAE,CAAA;IACzB,YAAY,GAAG,IAAI,CAAA;AACrB,CAAC,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ScrollEntry } from "./scrollMemory.ts";
|
|
2
|
+
/**
|
|
3
|
+
* How the history entry reaches `Main` without `Main` knowing a
|
|
4
|
+
* router exists.
|
|
5
|
+
*
|
|
6
|
+
* `@charcuterie/ui`'s main entry is router-free and stays that way —
|
|
7
|
+
* `sourceRules.test.ts` asserts it, and the fleet runs five
|
|
8
|
+
* different routers. So the entry arrives as a plain
|
|
9
|
+
* `{ key, path }`, and the one component that knows how to read it
|
|
10
|
+
* from react-router lives in the `./react-router` subpath.
|
|
11
|
+
*
|
|
12
|
+
* A context rather than a prop, because the alternative is a prop
|
|
13
|
+
* every app has to remember on every `Main` it renders. It was a
|
|
14
|
+
* prop first, exactly once, and three of the four apps that needed
|
|
15
|
+
* it did not have it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ScrollMemoryContext: import("react").Context<ScrollEntry | null>;
|
|
18
|
+
//# sourceMappingURL=scrollMemoryContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrollMemoryContext.d.ts","sourceRoot":"","sources":["../../src/Main/scrollMemoryContext.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,6CACS,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* How the history entry reaches `Main` without `Main` knowing a
|
|
4
|
+
* router exists.
|
|
5
|
+
*
|
|
6
|
+
* `@charcuterie/ui`'s main entry is router-free and stays that way —
|
|
7
|
+
* `sourceRules.test.ts` asserts it, and the fleet runs five
|
|
8
|
+
* different routers. So the entry arrives as a plain
|
|
9
|
+
* `{ key, path }`, and the one component that knows how to read it
|
|
10
|
+
* from react-router lives in the `./react-router` subpath.
|
|
11
|
+
*
|
|
12
|
+
* A context rather than a prop, because the alternative is a prop
|
|
13
|
+
* every app has to remember on every `Main` it renders. It was a
|
|
14
|
+
* prop first, exactly once, and three of the four apps that needed
|
|
15
|
+
* it did not have it.
|
|
16
|
+
*/
|
|
17
|
+
export const ScrollMemoryContext = createContext(null);
|
|
18
|
+
//# sourceMappingURL=scrollMemoryContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrollMemoryContext.js","sourceRoot":"","sources":["../../src/Main/scrollMemoryContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAIrC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC9B,aAAa,CAAqB,IAAI,CAAC,CAAA"}
|