@usableapp/cardds 0.7.46 → 0.7.48
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 +16 -0
- package/css/numbers.css +7 -1
- package/css/step.css +11 -0
- package/css/tokens.css +1 -0
- package/dist/cardds.css +19 -1
- package/dist/scaffold/Screen.js +9 -0
- package/dist/sheets/Sheet.d.ts +7 -1
- package/dist/sheets/Sheet.js +61 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.48 — 2026-10-03
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **A sheet going away has a rest of its own** (Lh 2026-10-03): when a `Sheet` slides to `away`, it is settled `--motion-sheet-rest` (0.2s) AFTER it landed — every time, whether or not another sheet follows. So `onSettled('away')` now fires 0.2s later than in 0.7.47 (slide 0.35s + rest 0.2s); an app that unmounts the sheet or moves on from `onSettled('away')` does so a beat later. A sheet asked up in the same box meanwhile waits until the one leaving is settled. What a member sees when one sheet follows another is as in 0.7.47 (down · the base · up); what changed is whose timeout it is — the sheet that slid out, not the one coming up. Only going away rests: peek ↔ full is as before. Nothing to change in a screen; add no timer of your own.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **A `Track` as the top of a map screen**: `<Screen fill top={<Track>…</Track>}>` over a `SheetStage` — the track is a layer on the stage (the map shows around it, to the screen's top), and the sheet at full stops 16px under it. Story `MapArea · TrackOnTop`.
|
|
12
|
+
|
|
13
|
+
## 0.7.47 — 2026-10-03
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **One sheet follows another: the first goes all the way down, the base is seen, then the next comes up** (Lh 2026-10-03). It is the `Sheet`'s own habit now: a sheet asked up while another sheet of the same box (`Step`, `SheetStage`) is leaving waits at `away` until that one has landed, the base shows for `--motion-sheet-rest` (0.2s), then it rises — 0.35s down · 0.2s base · 0.35s up, never two sheets in the box. Before, two sheets set in one render crossed (`CallSheet ways` going from one way to another, `Step · Calls`), and a sheet raised from the first one's `onSettled('away')` started in the very frame the first landed — one continuous move. Nothing to change in a screen: both wirings now play the same, and an app adds no timer of its own (remove one if you had it). `onSettled` of the sheet coming up fires later by the wait. With reduced motion there is no wait. New root token `--motion-sheet-rest`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **`TrackStep num`: the number sits in the middle of its round at every text size** — the round is cut from its own number (`1.877em`, 32px at the default text as before) and the digit is lifted onto the middle, as the SheetSteps' number. Knobs `--track-dot-size` · `--track-dot-lift`. At a large text a NUMBERED track's pill grows with its rounds and is taller than the top bar (160% text: 88px beside a 78px bar); an icon track keeps the bar's height.
|
|
20
|
+
|
|
5
21
|
## 0.7.46 — 2026-10-03
|
|
6
22
|
|
|
7
23
|
### Changed
|
package/css/numbers.css
CHANGED
|
@@ -89,7 +89,13 @@
|
|
|
89
89
|
background: var(--ink-rail);
|
|
90
90
|
color: var(--_ink);
|
|
91
91
|
}
|
|
92
|
-
.track__step > .track__dot {
|
|
92
|
+
.track__step > .track__dot { /* a number in the round (TrackStep num) */
|
|
93
|
+
display: grid; place-items: center; box-sizing: border-box;
|
|
94
|
+
font: var(--track-dot-font, var(--type-caption)); line-height: 1; font-variant-numeric: tabular-nums;
|
|
95
|
+
width: var(--track-dot-size, 1.877em); height: var(--track-dot-size, 1.877em); /* em, not rem (Lh 2026-10-03, as the SheetSteps' number and the StackSheet's count): the round is cut from ITS OWN
|
|
96
|
+
number's size — 1.877em of a 17px caption = the 32px of an icon's round; at a larger text it grows with its number, and the pill with it */
|
|
97
|
+
padding: 0 0 var(--track-dot-lift, 0.1em); /* the room UNDER the number lifts it onto the round's middle: a digit has no descender */
|
|
98
|
+
}
|
|
93
99
|
.track__step.is-done > .icon,
|
|
94
100
|
.track__step.is-done > .track__dot { background: var(--accent); color: var(--on-accent); }
|
|
95
101
|
|
package/css/step.css
CHANGED
|
@@ -150,6 +150,17 @@
|
|
|
150
150
|
margin-top: var(--track-top-lead, var(--sp-4));
|
|
151
151
|
margin-bottom: calc(var(--track-top-gap, var(--sp-4)) - var(--screen-gap));
|
|
152
152
|
}
|
|
153
|
+
/* …and over a STAGE (a map behind a sheet, Lh 2026-10-03): the stage is the whole screen, so the track is a LAYER on it — the map
|
|
154
|
+
shows around the pill — and the stage keeps the track's line free itself: a sheet at full stops --track-top-gap under the
|
|
155
|
+
track, as in a step. --top-h is the track's own height, measured by Screen (a numbered track grows with the text). */
|
|
156
|
+
.screen--fill > .track:first-child:has(+ .sheet-stage) {
|
|
157
|
+
position: absolute; z-index: 1;
|
|
158
|
+
inset: calc(var(--safe-top) + var(--track-top-lead, var(--sp-4))) var(--screen-pad) auto;
|
|
159
|
+
margin: 0;
|
|
160
|
+
}
|
|
161
|
+
.screen--fill > .track:first-child + .sheet-stage {
|
|
162
|
+
--_top: calc(var(--track-top-lead, var(--sp-4)) + var(--top-h, var(--header-h)) + var(--track-top-gap, var(--sp-4)));
|
|
163
|
+
}
|
|
153
164
|
/* a walk page that OPENS on a picture (Lh 2026-09-23): the picture takes the lead's room — as wide as the content (edge to edge), from the top edge, as tall as
|
|
154
165
|
the lead + the picture's own height (--page-media-h), square — so the words start where they always did */
|
|
155
166
|
.step > .pages > .page:has(> :is(img, .ph):first-child) { margin-top: 0; }
|
package/css/tokens.css
CHANGED
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
|
|
96
96
|
/* ---- motion ---- */
|
|
97
97
|
--motion-sheet: 0.35s;
|
|
98
|
+
--motion-sheet-rest: 0.2s; /* one sheet follows another: how long the base is seen between the one that left and the one that comes (Sheet.tsx reads it) */
|
|
98
99
|
--motion-quick: 0.2s; /* a small state flip that isn't a press: a chevron turning, a popover/banner opening */
|
|
99
100
|
--motion-slide: 0.22s; /* a centred card sliding out and the next in (card.css) — quick, a page turn (Lh, 2026-09-12) */
|
|
100
101
|
--motion-flip: 0.45s; /* a postcard turning over (media.css) — slow enough to read as a card in the hand */
|
package/dist/cardds.css
CHANGED
|
@@ -136,6 +136,7 @@
|
|
|
136
136
|
|
|
137
137
|
/* ---- motion ---- */
|
|
138
138
|
--motion-sheet: 0.35s;
|
|
139
|
+
--motion-sheet-rest: 0.2s; /* one sheet follows another: how long the base is seen between the one that left and the one that comes (Sheet.tsx reads it) */
|
|
139
140
|
--motion-quick: 0.2s; /* a small state flip that isn't a press: a chevron turning, a popover/banner opening */
|
|
140
141
|
--motion-slide: 0.22s; /* a centred card sliding out and the next in (card.css) — quick, a page turn (Lh, 2026-09-12) */
|
|
141
142
|
--motion-flip: 0.45s; /* a postcard turning over (media.css) — slow enough to read as a card in the hand */
|
|
@@ -1910,6 +1911,17 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1910
1911
|
margin-top: var(--track-top-lead, var(--sp-4));
|
|
1911
1912
|
margin-bottom: calc(var(--track-top-gap, var(--sp-4)) - var(--screen-gap));
|
|
1912
1913
|
}
|
|
1914
|
+
/* …and over a STAGE (a map behind a sheet, Lh 2026-10-03): the stage is the whole screen, so the track is a LAYER on it — the map
|
|
1915
|
+
shows around the pill — and the stage keeps the track's line free itself: a sheet at full stops --track-top-gap under the
|
|
1916
|
+
track, as in a step. --top-h is the track's own height, measured by Screen (a numbered track grows with the text). */
|
|
1917
|
+
.screen--fill > .track:first-child:has(+ .sheet-stage) {
|
|
1918
|
+
position: absolute; z-index: 1;
|
|
1919
|
+
inset: calc(var(--safe-top) + var(--track-top-lead, var(--sp-4))) var(--screen-pad) auto;
|
|
1920
|
+
margin: 0;
|
|
1921
|
+
}
|
|
1922
|
+
.screen--fill > .track:first-child + .sheet-stage {
|
|
1923
|
+
--_top: calc(var(--track-top-lead, var(--sp-4)) + var(--top-h, var(--header-h)) + var(--track-top-gap, var(--sp-4)));
|
|
1924
|
+
}
|
|
1913
1925
|
/* a walk page that OPENS on a picture (Lh 2026-09-23): the picture takes the lead's room — as wide as the content (edge to edge), from the top edge, as tall as
|
|
1914
1926
|
the lead + the picture's own height (--page-media-h), square — so the words start where they always did */
|
|
1915
1927
|
.step > .pages > .page:has(> :is(img, .ph):first-child) { margin-top: 0; }
|
|
@@ -2577,7 +2589,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2577
2589
|
background: var(--ink-rail);
|
|
2578
2590
|
color: var(--_ink);
|
|
2579
2591
|
}
|
|
2580
|
-
.track__step > .track__dot {
|
|
2592
|
+
.track__step > .track__dot { /* a number in the round (TrackStep num) */
|
|
2593
|
+
display: grid; place-items: center; box-sizing: border-box;
|
|
2594
|
+
font: var(--track-dot-font, var(--type-caption)); line-height: 1; font-variant-numeric: tabular-nums;
|
|
2595
|
+
width: var(--track-dot-size, 1.877em); height: var(--track-dot-size, 1.877em); /* em, not rem (Lh 2026-10-03, as the SheetSteps' number and the StackSheet's count): the round is cut from ITS OWN
|
|
2596
|
+
number's size — 1.877em of a 17px caption = the 32px of an icon's round; at a larger text it grows with its number, and the pill with it */
|
|
2597
|
+
padding: 0 0 var(--track-dot-lift, 0.1em); /* the room UNDER the number lifts it onto the round's middle: a digit has no descender */
|
|
2598
|
+
}
|
|
2581
2599
|
.track__step.is-done > .icon,
|
|
2582
2600
|
.track__step.is-done > .track__dot { background: var(--accent); color: var(--on-accent); }
|
|
2583
2601
|
|
package/dist/scaffold/Screen.js
CHANGED
|
@@ -28,6 +28,15 @@ export function Screen({ fill, palette, as = 'div', top, bottom, ready = true, c
|
|
|
28
28
|
}
|
|
29
29
|
return watchSize([bar], () => el.style.setProperty('--bottom-h', `${bar.offsetHeight}px`));
|
|
30
30
|
}, [bottom, ready]);
|
|
31
|
+
/* a TRACK as the top, over a stage: its height published as --top-h — the stage keeps that line free for it (step.css) */
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const el = own.current, track = el?.querySelector(':scope > .track:first-child');
|
|
34
|
+
if (!el || !track) {
|
|
35
|
+
el?.style.removeProperty('--top-h');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return watchSize([track], () => el.style.setProperty('--top-h', `${track.offsetHeight}px`));
|
|
39
|
+
}, [top, ready]);
|
|
31
40
|
const setRef = (node) => {
|
|
32
41
|
own.current = node;
|
|
33
42
|
if (typeof ref === 'function')
|
package/dist/sheets/Sheet.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
14
14
|
/** the sheet ASKS to move — the handle was tapped or dragged, a field in a peeked sheet took focus, the keyboard moved the cut.
|
|
15
15
|
* Controlled: set your state to it (or don't — the sheet stays). This is where an app ties a sheet to its router / back button. */
|
|
16
16
|
onStateChange?: (state: SheetState, reason: SheetChangeReason) => void;
|
|
17
|
-
/** the sheet has FINISHED
|
|
17
|
+
/** the sheet has FINISHED its move to `state` (its transition ended; at once where motion is off). Going `away` it is finished a beat
|
|
18
|
+
* AFTER it landed (`--motion-sheet-rest`) — its own timeout, every time. To leave the screen: set `away`, and take the
|
|
18
19
|
* sheet out of the tree here — `onSettled={(s) => s === 'away' && setMounted(false)}` — so it slides out instead of vanishing */
|
|
19
20
|
onSettled?: (state: SheetState) => void;
|
|
20
21
|
/** the states a drag snaps to and a reveal may climb through — "peek half full", or an array (default all four) */
|
|
@@ -37,6 +38,11 @@ export interface SheetProps extends Omit<ComponentProps<'article'>, 'onChange'>
|
|
|
37
38
|
* It ARRIVES and LEAVES by sliding, always: a sheet that mounts already up starts `away` for one frame and slides to its state, so
|
|
38
39
|
* `{open && <Sheet state="full">}` slides in. Leaving is the app's beat: set `away`, and unmount on `onSettled('away')` — unmount it
|
|
39
40
|
* while it is still up and it can only vanish (a dev warning says so).
|
|
41
|
+
*
|
|
42
|
+
* GOING AWAY HAS A REST: a sheet that slides out is settled a beat after it landed (`--motion-sheet-rest`, the base is seen) — a
|
|
43
|
+
* fixed habit of every sheet. A sheet asked up in the same box meanwhile waits for it: "one left, another came", never two sheets
|
|
44
|
+
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
45
|
+
* first's `onSettled`) — the app adds no timer.
|
|
40
46
|
*/
|
|
41
47
|
export declare function Sheet({ state, defaultState, onStateChange, onSettled, states, handle, className, children, onFocus, ...rest }: SheetProps): import("react").JSX.Element;
|
|
42
48
|
export {};
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
-
import { focusWhenActive, useControlled, useLatest, warnOnce, watchSize } from '../state.js';
|
|
3
|
+
import { focusWhenActive, reducedMotion, useControlled, useLatest, warnOnce, watchSize } from '../state.js';
|
|
4
4
|
import { cx } from '../cx.js';
|
|
5
5
|
import { word } from '../words.js';
|
|
6
6
|
const ORDER = ['peek', 'half', '3q', 'full']; // low → high; `away` is below them all and outside them
|
|
7
7
|
const RANK = { away: 0, peek: 1, half: 2, '3q': 3, full: 4 };
|
|
8
8
|
const TAP_REM = 0.5; // = --sp-2: a move shorter than this is a tap, not a drag
|
|
9
|
+
/* A SHEET THAT GOES AWAY IS NOT DONE WHEN IT LANDS (Lh 2026-10-03): it holds its box for a beat more (--motion-sheet-rest) — the base
|
|
10
|
+
is seen — and only then is it settled. `out` = the sheets still on their way out, per box; a sheet asked up in that box waits for it to empty. */
|
|
11
|
+
const out = new WeakMap();
|
|
12
|
+
const waiting = new WeakMap();
|
|
13
|
+
const leave = (box, key) => { if (!out.has(box))
|
|
14
|
+
out.set(box, new Set()); out.get(box).add(key); };
|
|
15
|
+
const gone = (box, key) => { const set = out.get(box); if (set?.delete(key) && !set.size)
|
|
16
|
+
[...(waiting.get(box) ?? [])].forEach((go) => go()); };
|
|
9
17
|
const ms = (t) => Math.max(0, ...t.split(',').map((v) => parseFloat(v) * (v.trim().endsWith('ms') ? 1 : 1000) || 0)); // a computed `0.35s, 0s` list → its longest, in ms
|
|
10
18
|
/**
|
|
11
19
|
* Sheet — the SINGLE sheet: one card as a bottom sheet holding what the member ACTS on. Always the full height of its
|
|
@@ -22,6 +30,11 @@ const ms = (t) => Math.max(0, ...t.split(',').map((v) => parseFloat(v) * (v.trim
|
|
|
22
30
|
* It ARRIVES and LEAVES by sliding, always: a sheet that mounts already up starts `away` for one frame and slides to its state, so
|
|
23
31
|
* `{open && <Sheet state="full">}` slides in. Leaving is the app's beat: set `away`, and unmount on `onSettled('away')` — unmount it
|
|
24
32
|
* while it is still up and it can only vanish (a dev warning says so).
|
|
33
|
+
*
|
|
34
|
+
* GOING AWAY HAS A REST: a sheet that slides out is settled a beat after it landed (`--motion-sheet-rest`, the base is seen) — a
|
|
35
|
+
* fixed habit of every sheet. A sheet asked up in the same box meanwhile waits for it: "one left, another came", never two sheets
|
|
36
|
+
* crossing and never one continuous move, whatever the app's wiring (both states set in one render, or the next raised from the
|
|
37
|
+
* first's `onSettled`) — the app adds no timer.
|
|
25
38
|
*/
|
|
26
39
|
export function Sheet({ state, defaultState = 'half', onStateChange, onSettled, states, handle = true, className, children, onFocus, ...rest }) {
|
|
27
40
|
const [current, request] = useControlled(state, defaultState, onStateChange);
|
|
@@ -31,8 +44,17 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
31
44
|
const was = useRef('away'); // mounted up = it came onto the screen: it rises from away like any arrival
|
|
32
45
|
const skipClick = useRef(false);
|
|
33
46
|
const [entered, setEntered] = useState(false); // false for the first frame: the class says `away`, so a sheet mounted up has somewhere to slide from
|
|
34
|
-
const
|
|
47
|
+
const me = useRef({}).current; // this sheet, in its box's list of sheets on their way out
|
|
48
|
+
const [held, setHeld] = useState(false); // true while it waits for the sheet going away before it to be settled
|
|
49
|
+
const [prev, setPrev] = useState('away'); // asked up from `away` (or mounted up): it holds first, and rises once its box is free
|
|
50
|
+
if (prev !== current) {
|
|
51
|
+
setPrev(current);
|
|
52
|
+
setHeld(prev === 'away' && current !== 'away');
|
|
53
|
+
}
|
|
54
|
+
const shown = entered && !held ? current : 'away';
|
|
55
|
+
const live = held ? 'away' : current; // where it is as far as its own habits go: a held sheet has not come up yet
|
|
35
56
|
const latest = useLatest({ current, onSettled });
|
|
57
|
+
const last = useRef('away'); // where it was last SHOWN (the settle effect keeps it)
|
|
36
58
|
const drag = useRef(null);
|
|
37
59
|
const allowed = useCallback(() => {
|
|
38
60
|
const list = (typeof states === 'string' ? states.split(/\s+/) : [...(states ?? [])]).filter((s) => ORDER.includes(s));
|
|
@@ -42,29 +64,54 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
42
64
|
void ref.current?.offsetHeight; // the browser styles the `away` frame first, or there is no transition to run
|
|
43
65
|
setEntered(true);
|
|
44
66
|
}, []);
|
|
67
|
+
/* it starts on its way out: said in the commit itself, before any sheet of the box decides to rise */
|
|
68
|
+
useLayoutEffect(() => {
|
|
69
|
+
const box = ref.current?.parentElement;
|
|
70
|
+
if (box && current === 'away' && last.current !== 'away' && !reducedMotion())
|
|
71
|
+
leave(box, me);
|
|
72
|
+
}, [current, me]);
|
|
73
|
+
/* held: it rises when no sheet of its box is on its way out — at once if none is */
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const box = ref.current?.parentElement;
|
|
76
|
+
if (!held)
|
|
77
|
+
return;
|
|
78
|
+
if (!box || !out.get(box)?.size) {
|
|
79
|
+
setHeld(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const go = () => setHeld(false);
|
|
83
|
+
if (!waiting.has(box))
|
|
84
|
+
waiting.set(box, new Set());
|
|
85
|
+
waiting.get(box).add(go);
|
|
86
|
+
return () => { waiting.get(box)?.delete(go); };
|
|
87
|
+
}, [held]);
|
|
45
88
|
/* it has finished moving: the transition ended (or at once where there is none) — say so once per move; a move cut short by another says nothing */
|
|
46
|
-
const last = useRef(shown);
|
|
47
89
|
useEffect(() => {
|
|
48
|
-
const el = ref.current;
|
|
90
|
+
const el = ref.current, box = el?.parentElement;
|
|
49
91
|
if (!el || last.current === shown)
|
|
50
92
|
return;
|
|
51
93
|
last.current = shown;
|
|
52
94
|
const cs = getComputedStyle(el);
|
|
53
|
-
let done = false;
|
|
95
|
+
let done = false, rest = 0;
|
|
96
|
+
const settled = () => { if (box)
|
|
97
|
+
gone(box, me); latest.current.onSettled?.(latest.current.current); };
|
|
54
98
|
const end = () => {
|
|
55
99
|
if (done)
|
|
56
100
|
return;
|
|
57
101
|
done = true;
|
|
58
102
|
clearTimeout(t);
|
|
59
103
|
el.removeEventListener('transitionend', onEnd);
|
|
60
|
-
|
|
104
|
+
if (shown !== 'away' || reducedMotion())
|
|
105
|
+
return settled();
|
|
106
|
+
rest = window.setTimeout(settled, ms(cs.getPropertyValue('--motion-sheet-rest') || '0s')); // landed: the base is seen for the rest, THEN it is settled
|
|
61
107
|
};
|
|
62
108
|
const onEnd = (e) => { if (e.target === el && e.propertyName === 'translate')
|
|
63
109
|
end(); };
|
|
64
110
|
el.addEventListener('transitionend', onEnd);
|
|
65
111
|
const t = setTimeout(end, ms(cs.transitionDuration) + ms(cs.transitionDelay) + 100); // no transition (motion off) or none came: settled anyway
|
|
66
|
-
return () => { done = true; clearTimeout(t); el.removeEventListener('transitionend', onEnd);
|
|
67
|
-
|
|
112
|
+
return () => { done = true; clearTimeout(t); clearTimeout(rest); el.removeEventListener('transitionend', onEnd); if (box && shown === 'away')
|
|
113
|
+
gone(box, me); }; // cut short on its way out (raised again, unmounted): the box is free
|
|
114
|
+
}, [shown, latest, me]);
|
|
68
115
|
/* taken out of the tree while it is still up — the box stays, the sheet goes: nothing to slide, so it vanishes. Said once in dev.
|
|
69
116
|
(A whole screen leaving takes the box too; StrictMode's trial unmount is over by the time the timer looks.) */
|
|
70
117
|
const alive = useRef(false);
|
|
@@ -85,17 +132,17 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
85
132
|
field), else the body's first field; with no field, a sheet that came onto the screen gives it to its handle. A field that already
|
|
86
133
|
has the cursor keeps it. */
|
|
87
134
|
useEffect(() => {
|
|
88
|
-
if (
|
|
89
|
-
home.current =
|
|
90
|
-
const rose =
|
|
135
|
+
if (live !== 'peek' && live !== 'away')
|
|
136
|
+
home.current = live;
|
|
137
|
+
const rose = live !== 'peek' && live !== 'away' && RANK[live] > RANK[was.current];
|
|
91
138
|
const from = was.current;
|
|
92
|
-
was.current =
|
|
139
|
+
was.current = live;
|
|
93
140
|
if (!rose)
|
|
94
141
|
return;
|
|
95
142
|
const cancel = focusWhenActive(ref.current, { scope: ':scope > .sheet__body', fallback: () => (from === 'away' ? handleRef.current : null) });
|
|
96
143
|
return () => { if (!cancel())
|
|
97
144
|
was.current = from; }; // cut short (StrictMode's trial run, or a new state mid-slide): the rise still counts from where it began
|
|
98
|
-
}, [
|
|
145
|
+
}, [live]);
|
|
99
146
|
/* geometry, read once per gesture — never stored: how far it has slid, and where each state rests, in px from its top at full */
|
|
100
147
|
const slid = (el) => {
|
|
101
148
|
const box = (el.offsetParent ?? el.parentElement);
|
|
@@ -246,5 +293,5 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
246
293
|
skipClick.current = true;
|
|
247
294
|
requestAnimationFrame(() => { skipClick.current = false; });
|
|
248
295
|
};
|
|
249
|
-
return (_jsxs("article", { ref: ref, className: cx('card sheet', `sheet--${shown}`, className), inert: current === 'away' || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className: "sheet__handle", type: "button", "aria-label": typeof handle === 'string' ? handle : word('resize'), onClick: tap, onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up })), children] }));
|
|
296
|
+
return (_jsxs("article", { ref: ref, className: cx('card sheet', `sheet--${shown}`, className), inert: current === 'away' || held || undefined, onFocus: focus, ...rest, children: [handle !== false && (_jsx("button", { ref: handleRef, className: "sheet__handle", type: "button", "aria-label": typeof handle === 'string' ? handle : word('resize'), onClick: tap, onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up })), children] }));
|
|
250
297
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.48",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|